Compare commits

..

No commits in common. "main" and "v1.8.1" have entirely different histories.
main ... v1.8.1

110 changed files with 645 additions and 1562 deletions

View file

@ -1,44 +0,0 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 0
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

View file

@ -2,32 +2,34 @@
name: Bug report
about: 'Create a report to help us improve'
title: ''
labels: 'bug'
labels: ''
assignees: ''
---
Please make sure you're on the latest version before submitting.
# What's Happening?
# What's Happening?
<!-- Describe here -->
## How to reproduce:
<!-- Describe here -->
## Affected Platforms:
- [ ] macOS
- [ ] Windows
- [ ] Linux (Specify)
- [ ] iOS
- [ ] Android
- [ ] macOS
- [ ] Windows
- [ ] Linux (Specify)
- [ ] iOS
- [ ] Android
Version:
Version:
## Browser:
- [ ] Chromium-based (ex: Brave or Chrome)
- [ ] Webkit-based (ex: Safari)
- [ ] Gecko-based (ex: Firefox)
- [ ] Chromium-based (ex: Brave or Chrome)
- [ ] Webkit-based (ex: Safari)
- [ ] Gecko-based (ex: Firefox)

View file

@ -1,15 +0,0 @@
---
name: Feature Request
about: 'Suggest a specific feature or enhancement'
title: ''
labels: 'enhancement'
assignees: ''
---
# What does the feature entail?
<!-- Describe here -->
# Why is this feature important?
<!-- Describe here -->

12
.github/ISSUE_TEMPLATE/new_instance.md vendored Normal file
View file

@ -0,0 +1,12 @@
---
name: New Instance
about: 'Add my public instance to the list'
title: "[INSTANCE] New public instance"
labels: ''
assignees: ''
---
Instance URL:
Region (Written Out - ex United States):
Operated by (Link to your site):

View file

@ -1,52 +0,0 @@
name: Create and publish a Docker image
# Configures this workflow to run every time a change is pushed to the branch called `release`.
on:
push:
branches: ['release']
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
build-and-push-image:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
#
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

3
.gitignore vendored
View file

@ -1,5 +1,4 @@
.env
docker-compose.yml
.DS_Store
*bin
/tmp
*bin

View file

@ -1,6 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}

View file

@ -1,30 +1,19 @@
FROM golang:1.22.1-alpine3.19 AS build
FROM golang:1.18-alpine
RUN apk add musl-dev
RUN apk add libc-dev
RUN apk add gcc
WORKDIR /app
COPY go.mod .
COPY go.sum .
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . .
COPY ./ /app
# Architecture and OS are set dynamically (by BuildKit)
ARG TARGETOS
ARG TARGETARCH
ENV CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH
RUN go build -o anonymousoverflow && go build -o healthcheck ./src/healthcheck
FROM scratch
COPY --from=build /app/anonymousoverflow /anonymousoverflow
COPY --from=build /app/healthcheck /healthcheck
COPY templates /templates
COPY public /public
COPY --from=build /etc/ssl/certs /etc/ssl/certs
HEALTHCHECK --interval=60s --timeout=5s --start-period=2s --retries=3 CMD [ "/healthcheck","http://localhost:8080/healthz" ]
RUN go build -o /anonymousoverflow
EXPOSE 8080

View file

@ -6,15 +6,33 @@ This project is super lightweight by design. The UI is simple and the frontend i
## Screenshots
![Home](./docs/screenshots/home_dark.webp)
![Home](https://files.horizon.pics/e2b9275c-1409-4978-801b-de981a8d3ae9?a=1&mime1=image&mime2=png)
![Question](./docs/screenshots/question_dark.webp)
![Question](https://files.horizon.pics/0f6b0036-87f0-4acd-9a0f-936b5c397a73?a=1&mime1=image&mime2=png)
![Answer](./docs/screenshots/answers_light.webp)
![Answer](https://files.horizon.pics/861ec510-644b-43f2-9439-0a2cae841422?a=1&mime1=image&mime2=png)
## Instances
## Clearnet Instances
Visit the [AnonymousOverflow Hub](https://aohub.httpjames.space) for a list of 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) |
| [overflow.777.tf](https://overflow.777.tf/) | The Netherlands | Operated by [Jae](https://777.tf) |
| [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) |
| [anonymousoverflow.esmailelbob.xyz](https://anonymousoverflow.esmailelbob.xyz/) | Canada | Operated by [Esmail EL BoB](https://esmailelbob.xyz) |
| [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/) |
| [ao.foss.wtf](https://ao.foss.wtf) | Germany | Operated by [foss.wtf](https://foss.wtf) |
| [overflow.hostux.net](https://overflow.hostux.net/) | France | Operated by [Hostux](https://hostux.net/) |
## 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) |
| [anonymousoverflow.esmail5pdn24shtvieloeedh7ehz3nrwcdivnfhfcedl7gf4kwddhkqd.onion](http://anonymousoverflow.esmail5pdn24shtvieloeedh7ehz3nrwcdivnfhfcedl7gf4kwddhkqd.onion) | Canada | Operated by [Esmail EL BoB](https://esmailelbob.xyz) |
## Why use AnonymousOverflow over StackOverflow?
@ -41,9 +59,7 @@ StackOverflow has a cluttered UI that might distract you from the content you're
## How to make Stack Overflow links take you to AnonymousOverflow automatically
The open-source [Libredirect](https://github.com/libredirect/libredirect) extension for Firefox and Chromium-based desktop browsers has support for redirections to AnonymousOverflow. To enable this, simply open the extension settings, click on Stack Overflow, then toggle "Enable". That's it, now Stack Overflow links will go to AnonymousOverflow.
The open-source [Proxy_Redirect](https://openuserjs.org/scripts/sjehuda/Proxy_Redirect) user.js script for web browsers with userscript support. You can install it with a web extension like [Greasemonkey](https://greasespot.net/), [Tampermonkey](https://tampermonkey.net/) or [Violentmonkey](https://violentmonkey.github.io/). Once installed, Stack Overflow links will go to AnonymousOverflow.
The open-source [Libredirect](https://github.com/libredirect/libredirect) extension for Firefox and Chromium-based desktop browsers has support for redirections to AnonymousOverflow. To enable this, simply open the extension settings, click on Stack Overflow, then toggle "Enable". That's it, now Stack Overflow links go to AnonymousOverflow.
## How it works
@ -69,8 +85,8 @@ You can easily convert StackOverflow URLs to AnonymousOverflow ones by adding th
javascript: (function () {
window.location = window.location
.toString()
.replace(/stackoverflow\.com/, 'code.whatever.social')
})()
.replace(/stackoverflow\.com/, "code.whatever.social");
})();
```
Replace `code.whatever.social` with the domain name of the instance you're using if needed.
@ -90,4 +106,3 @@ Read the [wiki page](https://github.com/httpjamesm/AnonymousOverflow/wiki/Deploy
- [goquery](https://github.com/PuerkitoBio/goquery) under the [BSD 3-Clause License](https://github.com/PuerkitoBio/goquery/blob/master/LICENSE)
- [resty](https://github.com/go-resty/resty) under the [MIT License](https://github.com/go-resty/resty/blob/master/LICENSE)
- [Chroma](https://github.com/alecthomas/chroma) under the [MIT License](https://github.com/alecthomas/chroma/blob/master/COPYING)
- [KaTeX](https://github.com/KaTeX/KaTeX) under the [MIT License](https://github.com/KaTeX/KaTeX/blob/main/LICENSE)

View file

@ -1,3 +1,3 @@
package config
var Version = "1.13.0"
var Version = "1.8.1"

View file

@ -1,12 +1,14 @@
version: '3'
services:
anonymousoverflow:
container_name: 'app'
image: 'ghcr.io/httpjamesm/anonymousoverflow:release'
environment:
- APP_URL=https://domain.com
- JWT_SIGNING_SECRET=secret
ports:
- '80:8080'
restart: 'always'
anonymousoverflow:
container_name: 'app'
build:
context: .
network: 'host'
environment:
- APP_URL=https://domain.com
- JWT_SIGNING_SECRET=secret
ports:
- '80:8080'
restart: 'always'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

72
go.mod
View file

@ -3,65 +3,33 @@ module anonymousoverflow
go 1.19
require (
github.com/PuerkitoBio/goquery v1.9.1
github.com/PuerkitoBio/goquery v1.8.0
github.com/alecthomas/chroma v0.10.0
github.com/gin-gonic/gin v1.10.0
github.com/go-resty/resty/v2 v2.12.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/joho/godotenv v1.5.1
github.com/gin-gonic/gin v1.8.2
github.com/go-resty/resty/v2 v2.7.0
)
require (
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/influxdata/influxdb-client-go/v2 v2.13.0 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/oapi-codegen/runtime v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/redis/go-redis/v9 v9.5.3 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tavsec/gin-healthcheck v1.6.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.mongodb.org/mongo-driver v1.15.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/ugorji/go/codec v1.2.8 // indirect
golang.org/x/crypto v0.4.0 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

189
go.sum
View file

@ -1,113 +1,42 @@
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA=
github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM=
github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@ -118,40 +47,24 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -159,122 +72,34 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tavsec/gin-healthcheck v1.6.2 h1:F89IFXXtYOy3p4gne8WFkos3r7vjMbE+R3C/v70dTW0=
github.com/tavsec/gin-healthcheck v1.6.2/go.mod h1:VcZ4f44KqMnwbzRBrr7VYni2GmkMErd/44QuM5Dy/YI=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0=
github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -286,5 +111,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View file

@ -1,237 +0,0 @@
{
"clearnet": [
{
"url": "https://code.whatever.social",
"regions": ["Canada", "United States"],
"operators": ["https://whatever.social", "https://httpjames.space"]
},
{
"url": "https://ao.vern.cc",
"regions": ["United States"],
"operators": ["https://vern.cc"]
},
{
"url": "https://overflow.smnz.de",
"regions": ["Germany"],
"operators": ["https://smnz.de"]
},
{
"url": "https://overflow.lunar.icu",
"regions": ["Germany"],
"operators": ["https://lunar.icu/"]
},
{
"url": "https://overflow.adminforge.de/",
"regions": ["Germany"],
"operators": ["https://adminforge.de/"]
},
{
"url": "https://overflow.hostux.net/",
"regions": ["France"],
"operators": ["https://hostux.net/"]
},
{
"url": "https://overflow.projectsegfau.lt/",
"regions": ["United States", "Germany", "India"],
"operators": ["https://projectsegfau.lt/"]
},
{
"url": "https://code.xbdm.fun",
"regions": ["Germany"],
"operators": ["https://xbdm.fun"]
},
{
"url": "https://overflow.fascinated.cc/",
"regions": ["Germany"],
"operators": ["https://fascinated.cc/"]
},
{
"url": "https://ao.bloat.cat",
"regions": ["Germany"],
"operators": ["https://bloat.cat"]
},
{
"url": "https://anonoverflow.frontendfriendly.xyz/",
"regions": ["United States"],
"operators": ["https://frontendfriendly.xyz/"]
},
{
"url": "https://ao.owo.si/",
"regions": ["Germany"],
"operators": ["https://owo.si/"]
},
{
"url": "https://overflow.datura.network/",
"regions": ["Germany"],
"operators": ["https://datura.network"]
},
{
"url": "https://overflow.freedit.eu",
"regions": ["United States"],
"operators": ["https://freedit.eu"]
},
{
"url": "https://ao.rootdo.com",
"regions": ["Germany"],
"operators": ["https://rootdo.com"]
},
{
"url": "https://anonoverflow.hyperreal.coffee",
"regions": ["United States"],
"operators": ["https://hyperreal.coffee"]
},
{
"url": "https://o.sudovanilla.org",
"regions": ["United States"],
"operators": ["https://sudovanilla.org"]
},
{
"url": "https://anonymousoverflow.privacyfucking.rocks/",
"regions": ["Germany"],
"operators": ["https://privacyfucking.rocks"]
},
{
"url": "https://exchange.seitan-ayoub.lol",
"regions": ["Germany"],
"operators": ["https://seitan-ayoub.lol"]
},
{
"url": "https://overflow.r4fo.com",
"regions": ["The Netherlands"],
"operators": ["https://r4fo.com"]
},
{
"url": "https://overflow.ducks.party",
"regions": ["The Netherlands"],
"operators": ["https://ducks.party"]
},
{
"url": "https://ao.ngn.tf",
"regions": ["Turkey"],
"operators": ["https://ngn.tf"]
},
{
"url": "https://overflow.snine.nl",
"regions": ["The Netherlands"],
"operators": ["https://snine.nl"]
},
{
"url": "https://anonymousoverflow.privacyredirect.com",
"regions": ["Finland"],
"operators": ["https://privacyredirect.com/"]
},
{
"url": "https://soflow.nerdvpn.de",
"regions": ["Ukraine"],
"operators": ["https://nerdvpn.de"]
},
{
"url": "https://overflow.einfachzocken.eu/",
"regions": ["Germany"],
"operators": ["https://einfachzocken.eu"]
},
{
"url": "https://overflow.seasi.dev/",
"regions": ["Singapore"],
"operators": ["https://seasi.dev/"]
},
{
"url": "https://anonymousoverflow.catsarch.com",
"regions": ["United States"],
"operators": ["https://catsarch.com"]
},
{
"url": "https://overflow.darkness.services/",
"regions": ["United States"],
"operators": ["https://zzz.darkness.services"]
},
{
"url": "https://anonflow.aketawi.space/",
"regions": ["Russia"],
"operators": ["https://www.aketawi.space/"]
},
{
"url": "https://anonymousoverflow.gitro.xyz",
"regions": ["Germany"],
"operators": ["https://gitro.xyz"]
},
{
"url": "https://ao.bunk.lol",
"regions": ["Iceland"],
"operators": ["https://bunk.lol"]
},
{
"url": "https://o.iii.st/",
"regions": ["Germany"],
"operators": ["https://iii.st/"]
},
{
"url": "https://overflow.canine.tools/",
"regions": ["United States"],
"operators": ["https://canine.tools/"]
}
],
"onion": [
{
"url": "http://ao.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion",
"regions": ["United States"],
"operators": ["https://vern.cc"]
},
{
"url": "http://overflow.pjsfkvpxlinjamtawaksbnnaqs2fc2mtvmozrzckxh7f3kis6yea25ad.onion/",
"regions": ["Germany"],
"operators": ["https://projectsegfau.lt/"]
},
{
"url": "http://overflow.daturab6drmkhyeia4ch5gvfc2f3wgo6bhjrv3pz6n7kxmvoznlkq4yd.onion/",
"regions": ["Germany"],
"operators": ["https://datura.network"]
},
{
"url": "http://ao.pk47sgwhncn5cgidm7bofngmh7lc7ukjdpk5bjwfemmyp27ovl25ikyd.onion/",
"regions": ["Germany"],
"operators": ["https://owo.si/"]
},
{
"url": "http://overflow.r4focoma7gu2zdwwcjjad47ysxt634lg73sxmdbkdozanwqslho5ohyd.onion",
"regions": ["The Netherlands"],
"operators": ["https://r4fo.com"]
},
{
"url": "http://anonymousoverflow.catsarchywsyuss6jdxlypsw5dc7owd5u5tr6bujxb7o6xw2hipqehyd.onion/",
"regions": ["United States"],
"operators": ["https://catsarch.com"]
},
{
"url": "http://overflow.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion/",
"regions": ["United States"],
"operators": [
"http://darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion/"
]
},
{
"url": "http://o.zx56doutynmbgezxtpccduajwcblzx7fgio2yuy57a3jingco2c6fvqd.onion/",
"regions": ["Germany"],
"operators": ["https://iii.st/"]
}
],
"i2p": [
{
"url": "http://vernmzgraj6aaoafmehupvtkkynpaa67rxcdj2kinwiy6konn6rq.b32.i2p",
"regions": ["United States"],
"operators": ["https://vern.cc"]
},
{
"url": "http://ay7akchgdh76r4lc62hzd52z6xqoh67loototsetvqxo5o7ngo5q.b32.i2p/",
"regions": ["Germany"],
"operators": ["https://owo.si/"]
},
{
"url": "http://ocp7zhdsbl2mjabv5ma5jvbzg2dqzglieayjvyj4j2r7qvsqlboa.b32.i2p/",
"regions": ["United States"],
"operators": ["https://catsarch.com"]
}
]
}

41
main.go
View file

@ -8,9 +8,6 @@ import (
"os"
"github.com/gin-gonic/gin"
healthcheck "github.com/tavsec/gin-healthcheck"
"github.com/tavsec/gin-healthcheck/checks"
"github.com/tavsec/gin-healthcheck/config"
)
func main() {
@ -38,6 +35,7 @@ func main() {
r.Use(gin.Recovery())
r.Use(middleware.XssPreventionHeaders())
r.Use(middleware.NoCacheMiddleware())
r.Use(middleware.OptionsMiddleware())
r.Use(middleware.Ratelimit())
@ -52,28 +50,6 @@ func main() {
r.GET("/", routes.GetHome)
r.POST("/", routes.PostHome)
r.GET("/a/:id", routes.RedirectShortenedOverflowURL)
r.GET("/a/:id/:answerId", routes.RedirectShortenedOverflowURL)
r.GET("/q/:id", routes.RedirectShortenedOverflowURL)
r.GET("/q/:id/:answerId", routes.RedirectShortenedOverflowURL)
exchangeRouter := r.Group("/exchange/:sub")
{
exchangeRouter.GET("/questions/:id/:title", routes.ViewQuestion)
exchangeRouter.GET("/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")))
})
exchangeRouter.GET("/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")))
})
exchangeRouter.GET("/q/:id/:answerId", routes.RedirectShortenedOverflowURL)
exchangeRouter.GET("/q/:id", routes.RedirectShortenedOverflowURL)
exchangeRouter.GET("/a/:id/:answerId", routes.RedirectShortenedOverflowURL)
exchangeRouter.GET("/a/:id", routes.RedirectShortenedOverflowURL)
}
r.GET("/questions/:id", func(c *gin.Context) {
// redirect user to the question with the title
c.Redirect(302, fmt.Sprintf("/questions/%s/placeholder", c.Param("id")))
@ -83,14 +59,17 @@ func main() {
// 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")))
})
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("/version", routes.GetVersion)
soPingCheck := checks.NewPingCheck("https://stackoverflow.com", "GET", 5000, nil, nil)
sePingCheck := checks.NewPingCheck("https://stackexchange.com", "GET", 5000, nil, nil)
healthcheck.New(r, config.DefaultConfig(), []checks.Check{soPingCheck, sePingCheck})
r.Run(fmt.Sprintf("%s:%s", host, port))
}

BIN
public/codecircles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 48">
<circle cx="14.5" cy="33.5" r="14.5" fill="#8cffc0"/>
<circle cx="14.5" cy="14.5" r="14.5" fill="#fff"/>
<circle cx="34.5" cy="14.5" r="14.5" fill="#8cffc0"/>
<circle cx="34.5" cy="33.5" r="14.5" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -1,67 +1,37 @@
:root {
--code-bg: #36383d;
--code-fg: #ffffff;
}
:root,
[data-theme="dark"] {
--main-bg: #1b1f26;
--text-color: #fff;
--muted-text-color: #b3b3b3;
--code-bg: #36383d;
--input-bg: #2b303b;
--input-bg-hover: #3b404b;
--meta-bg: #525262;
--meta-bg: rgb(82, 82, 98);
--divider-color: #42464e;
--link-color: #92adff;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]) {
--main-bg: #dbdbdb;
--text-color: #000;
--muted-text-color: #636363;
--input-bg: #bcbcbc;
--input-bg-hover: #a8a8a8;
--meta-bg: #aaa8a8;
--divider-color: #b5b5b5;
--link-color: #335ad0;
}
}
[data-theme="light"] {
--main-bg: #dbdbdb;
--main-bg: rgb(219, 219, 219);
--text-color: #000;
--muted-text-color: #636363;
--input-bg: #bcbcbc;
--input-bg-hover: #a8a8a8;
--meta-bg: #aaa8a8;
--code-bg: #36383d;
--input-bg: rgb(188, 188, 188);
--input-bg-hover: rgb(168, 168, 168);
--meta-bg: rgb(170, 168, 168);
--divider-color: #b5b5b5;
--link-color: #335ad0;
}
a {
color: var(--link-color);
word-wrap: break-word;
}
html {
margin: auto;
max-width: 40rem;
}
@media only screen and (max-width: calc(40rem + 2rem)) {
body {
padding-left: 1rem;
padding-right: 1rem;
}
}
body {
background-color: var(--main-bg);
color: var(--text-color);
font-family: sans-serif;
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
}
.icon {
@ -75,21 +45,9 @@ body {
.icon img {
background: white;
border-radius: 50%;
padding: 0.25rem;
padding: .25rem;
}
details {
cursor: pointer;
}
.d-flex {
display: flex;
}
.fd-column {
flex-direction: column;
}
.fw-nowrap {
flex-wrap: nowrap;
}
}

View file

@ -1,10 +1,21 @@
body {
background-color: var(--main-bg);
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
margin: 0;
color: var(--text-color);
}
.container {
margin: auto;
width: 40rem;
}
.footer {
@ -33,7 +44,7 @@ body {
.view-input:focus {
outline: none;
border: 2px solid #a8a8a8;
border: 2px solid rgb(168, 168, 168);
}
.view-button {
@ -60,7 +71,7 @@ body {
}
.error {
background-color: #ff8181;
background-color: rgb(255, 129, 129);
}
.error,
@ -93,3 +104,10 @@ body {
width: 2rem;
height: 2rem;
}
@media screen and (max-width: 800px) {
body {
padding: 1rem;
box-sizing: border-box;
}
}

View file

@ -1,125 +0,0 @@
<h1><a href="https://katex.org/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://katex.org/img/katex-logo.svg">
<img alt="KaTeX" width=130 src="https://katex.org/img/katex-logo-black.svg">
</picture>
</a></h1>
[![npm](https://img.shields.io/npm/v/katex.svg)](https://www.npmjs.com/package/katex)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![CI](https://github.com/KaTeX/KaTeX/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/KaTeX/KaTeX/actions?query=workflow%3ACI)
[![codecov](https://codecov.io/gh/KaTeX/KaTeX/branch/main/graph/badge.svg)](https://codecov.io/gh/KaTeX/KaTeX)
[![Discussions](https://img.shields.io/badge/Discussions-join-brightgreen)](https://github.com/KaTeX/KaTeX/discussions)
[![jsDelivr](https://data.jsdelivr.com/v1/package/npm/katex/badge?style=rounded)](https://www.jsdelivr.com/package/npm/katex)
![katex.min.js size](https://img.badgesize.io/https://unpkg.com/katex/dist/katex.min.js?compression=gzip)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/KaTeX/KaTeX)
[![Financial Contributors on Open Collective](https://opencollective.com/katex/all/badge.svg?label=financial+contributors)](https://opencollective.com/katex)
KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web.
* **Fast:** KaTeX renders its math synchronously and doesn't need to reflow the page. See how it compares to a competitor in [this speed test](https://www.intmath.com/cg5/katex-mathjax-comparison.php).
* **Print quality:** KaTeX's layout is based on Donald Knuth's TeX, the gold standard for math typesetting.
* **Self contained:** KaTeX has no dependencies and can easily be bundled with your website resources.
* **Server side rendering:** KaTeX produces the same output regardless of browser or environment, so you can pre-render expressions using Node.js and send them as plain HTML.
KaTeX is compatible with all major browsers, including Chrome, Safari, Firefox, Opera, Edge, and IE 11.
KaTeX supports much (but not all) of LaTeX and many LaTeX packages. See the [list of supported functions](https://katex.org/docs/supported.html).
Try out KaTeX [on the demo page](https://katex.org/#demo)!
## Getting started
### Starter template
```html
<!DOCTYPE html>
<!-- KaTeX requires the use of the HTML5 doctype. Without it, KaTeX may not render properly -->
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
<!-- The loading of KaTeX is deferred to speed up page rendering -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
<!-- To automatically render math in text elements, include the auto-render extension: -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"
onload="renderMathInElement(document.body);"></script>
</head>
...
</html>
```
You can also [download KaTeX](https://github.com/KaTeX/KaTeX/releases) and host it yourself.
For details on how to configure auto-render extension, refer to [the documentation](https://katex.org/docs/autorender.html).
### API
Call `katex.render` to render a TeX expression directly into a DOM element.
For example:
```js
katex.render("c = \\pm\\sqrt{a^2 + b^2}", element, {
throwOnError: false
});
```
Call `katex.renderToString` to generate an HTML string of the rendered math,
e.g., for server-side rendering. For example:
```js
var html = katex.renderToString("c = \\pm\\sqrt{a^2 + b^2}", {
throwOnError: false
});
// '<span class="katex">...</span>'
```
Make sure to include the CSS and font files in both cases.
If you are doing all rendering on the server, there is no need to include the
JavaScript on the client.
The examples above use the `throwOnError: false` option, which renders invalid
inputs as the TeX source code in red (by default), with the error message as
hover text. For other available options, see the
[API documentation](https://katex.org/docs/api.html),
[options documentation](https://katex.org/docs/options.html), and
[handling errors documentation](https://katex.org/docs/error.html).
## Demo and Documentation
Learn more about using KaTeX [on the website](https://katex.org)!
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute code. If you'd like to help, see [our guide to contributing code](CONTRIBUTING.md).
<a href="https://github.com/KaTeX/KaTeX/graphs/contributors"><img src="https://contributors-svg.opencollective.com/katex/contributors.svg?width=890&button=false" alt="Code contributors" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community.
#### Individuals
<a href="https://opencollective.com/katex"><img src="https://opencollective.com/katex/individuals.svg?width=890" alt="Contribute on Open Collective"></a>
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website.
<a href="https://opencollective.com/katex/organization/0/website"><img src="https://opencollective.com/katex/organization/0/avatar.svg" alt="Organization 1"></a>
<a href="https://opencollective.com/katex/organization/1/website"><img src="https://opencollective.com/katex/organization/1/avatar.svg" alt="Organization 2"></a>
<a href="https://opencollective.com/katex/organization/2/website"><img src="https://opencollective.com/katex/organization/2/avatar.svg" alt="Organization 3"></a>
<a href="https://opencollective.com/katex/organization/3/website"><img src="https://opencollective.com/katex/organization/3/avatar.svg" alt="Organization 4"></a>
<a href="https://opencollective.com/katex/organization/4/website"><img src="https://opencollective.com/katex/organization/4/avatar.svg" alt="Organization 5"></a>
<a href="https://opencollective.com/katex/organization/5/website"><img src="https://opencollective.com/katex/organization/5/avatar.svg" alt="Organization 6"></a>
<a href="https://opencollective.com/katex/organization/6/website"><img src="https://opencollective.com/katex/organization/6/avatar.svg" alt="Organization 7"></a>
<a href="https://opencollective.com/katex/organization/7/website"><img src="https://opencollective.com/katex/organization/7/avatar.svg" alt="Organization 8"></a>
<a href="https://opencollective.com/katex/organization/8/website"><img src="https://opencollective.com/katex/organization/8/avatar.svg" alt="Organization 9"></a>
<a href="https://opencollective.com/katex/organization/9/website"><img src="https://opencollective.com/katex/organization/9/avatar.svg" alt="Organization 10"></a>
## License
KaTeX is licensed under the [MIT License](https://opensource.org/licenses/MIT).

View file

@ -1 +0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},r={};function n(e){var i=r[e];if(void 0!==i)return i.exports;var a=r[e]={exports:{}};return t[e](a,a.exports,n),a.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var i={};return function(){n.d(i,{default:function(){return s}});var e=n(771),t=n.n(e),r=function(e,t,r){for(var n=r,i=0,a=e.length;n<t.length;){var o=t[n];if(i<=0&&t.slice(n,n+a)===e)return n;"\\"===o?n++:"{"===o?i++:"}"===o&&i--,n++}return-1},a=/^\\begin{/,o=function(e,t){for(var n,i=[],o=new RegExp("("+t.map((function(e){return e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")})).join("|")+")");-1!==(n=e.search(o));){n>0&&(i.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));var l=t.findIndex((function(t){return e.startsWith(t.left)}));if(-1===(n=r(t[l].right,e,t[l].left.length)))break;var d=e.slice(0,n+t[l].right.length),s=a.test(d)?d:e.slice(t[l].left.length,n);i.push({type:"math",data:s,rawData:d,display:t[l].display}),e=e.slice(n+t[l].right.length)}return""!==e&&i.push({type:"text",data:e}),i},l=function(e,r){var n=o(e,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;for(var i=document.createDocumentFragment(),a=0;a<n.length;a++)if("text"===n[a].type)i.appendChild(document.createTextNode(n[a].data));else{var l=document.createElement("span"),d=n[a].data;r.displayMode=n[a].display;try{r.preProcess&&(d=r.preProcess(d)),t().render(d,l,r)}catch(e){if(!(e instanceof t().ParseError))throw e;r.errorCallback("KaTeX auto-render: Failed to parse `"+n[a].data+"` with ",e),i.appendChild(document.createTextNode(n[a].rawData));continue}i.appendChild(l)}return i},d=function e(t,r){for(var n=0;n<t.childNodes.length;n++){var i=t.childNodes[n];if(3===i.nodeType){for(var a=i.textContent,o=i.nextSibling,d=0;o&&o.nodeType===Node.TEXT_NODE;)a+=o.textContent,o=o.nextSibling,d++;var s=l(a,r);if(s){for(var f=0;f<d;f++)i.nextSibling.remove();n+=s.childNodes.length-1,t.replaceChild(s,i)}else n+=d}else 1===i.nodeType&&function(){var t=" "+i.className+" ";-1===r.ignoredTags.indexOf(i.nodeName.toLowerCase())&&r.ignoredClasses.every((function(e){return-1===t.indexOf(" "+e+" ")}))&&e(i,r)}()}},s=function(e,t){if(!e)throw new Error("No element provided to render");var r={};for(var n in t)t.hasOwnProperty(n)&&(r[n]=t[n]);r.delimiters=r.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],r.ignoredTags=r.ignoredTags||["script","noscript","style","textarea","pre","code","option"],r.ignoredClasses=r.ignoredClasses||[],r.errorCallback=r.errorCallback||console.error,r.macros=r.macros||{},d(e,r)}}(),i=i.default}()}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,27 @@
body {
margin: 0;
width: 100vw;
height: 100vh;
overflow-x: hidden;
background-color: var(--main-bg);
color: var(--text-color);
font-family: sans-serif;
display: flex;
justify-content: center;
padding-left: 5rem;
padding-right: 5rem;
padding-top: 2rem;
box-sizing: border-box;
}
.parent {
max-width: 90%;
width: fit-content;
}
.header {
@ -9,7 +31,7 @@ body {
.card-tags {
display: flex;
gap: 0.5rem;
gap: .5rem;
}
.card-tags .tag {
@ -25,14 +47,14 @@ code {
background-color: var(--code-bg);
padding: 0.15rem;
border-radius: 5px;
color: var(--code-fg);
color: white;
}
pre {
background-color: var(--code-bg);
padding: 1rem;
border-radius: 5px;
color: var(--code-fg);
color: var(--text-color);
overflow-x: auto;
line-height: 1.35;
}
@ -72,7 +94,7 @@ img {
margin-bottom: 1rem;
background-color: var(--meta-bg);
color: var(--text-color);
display: flex;
justify-content: space-between;
align-items: center;
@ -118,21 +140,8 @@ img {
margin-bottom: 1rem;
}
.s-notice .d-flex {
display: flex;
}
.s-notice .mr8 {
margin-right: 1rem;
}
.s-notice svg {
height: 1rem;
}
.answers-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
@ -166,3 +175,10 @@ img {
justify-content: center;
align-items: center;
}
@media only screen and (max-width: 800px) {
body {
padding-left: 1rem;
padding-right: 1rem;
}
}

View file

@ -1,12 +0,0 @@
const doRender = () => {
renderMathInElement(document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '\\[', right: '\\]', display: true },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
],
})
}
doRender();

View file

@ -1,20 +0,0 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Expected URL as command-line argument")
os.Exit(1)
}
url := os.Args[1]
fmt.Println(url)
if _, err := http.Get(url); err != nil {
os.Exit(1)
}
}

12
src/middleware/noCache.go Normal file
View file

@ -0,0 +1,12 @@
package middleware
import "github.com/gin-gonic/gin"
func NoCacheMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
c.Next()
}
}

View file

@ -1,18 +1,30 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
import "github.com/gin-gonic/gin"
func OptionsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("disable_images", false)
c.Set("theme", "dark")
imagesCookie, err := c.Cookie("disable_images")
if err == nil {
if imagesCookie == "true" {
c.Set("disable_images", true)
}
if err != nil {
c.Next()
return
}
if imagesCookie == "true" {
c.Set("disable_images", true)
}
themeCookie, err := c.Cookie("theme")
if err != nil {
c.Next()
return
}
if themeCookie == "light" {
c.Set("theme", "light")
}
c.Next()

View file

@ -47,6 +47,7 @@ func Ratelimit() gin.HandlerFunc {
if val.(int) > 30 {
c.HTML(429, "home.html", gin.H{
"errorMessage": "You have exceeded the request limit. Please try again in a minute.",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
c.Abort()

View file

@ -2,7 +2,6 @@ package routes
import (
"anonymousoverflow/config"
"anonymousoverflow/src/utils"
"fmt"
"regexp"
"strings"
@ -11,10 +10,9 @@ import (
)
func GetHome(c *gin.Context) {
theme := utils.GetThemeFromEnv()
c.HTML(200, "home.html", gin.H{
"version": config.Version,
"theme": theme,
"theme": c.MustGet("theme").(string),
})
}
@ -22,41 +20,7 @@ type urlConversionRequest struct {
URL string `form:"url" binding:"required"`
}
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)
}
}
var stackExchangeRegex = regexp.MustCompile(`https://(.+).stackexchange.com/questions/`)
func PostHome(c *gin.Context) {
body := urlConversionRequest{}
@ -64,20 +28,32 @@ func PostHome(c *gin.Context) {
if err := c.ShouldBind(&body); err != nil {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid request body",
"theme": c.MustGet("theme").(string),
})
return
}
translated := translateUrl(body.URL)
soLink := body.URL
if translated == "" {
theme := utils.GetThemeFromEnv()
// validate URL
isStackOverflow := strings.HasPrefix(soLink, "https://stackoverflow.com/questions/")
isStackExchange := stackExchangeRegex.MatchString(soLink)
if !isStackExchange && !isStackOverflow {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid stack overflow/exchange URL",
"theme": theme,
"theme": c.MustGet("theme").(string),
})
return
}
c.Redirect(302, translated)
// if stack overflow, trim https://stackoverflow.com
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))))
}

View file

@ -1,32 +0,0 @@
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")
}

View file

@ -2,8 +2,9 @@ package routes
import (
"anonymousoverflow/config"
"anonymousoverflow/src/utils"
"fmt"
"os"
"strings"
"github.com/gin-gonic/gin"
)
@ -18,12 +19,30 @@ func ChangeOptions(c *gin.Context) {
text = "enabled"
}
c.SetCookie("disable_images", fmt.Sprintf("%t", !c.MustGet("disable_images").(bool)), 60*60*24*365*10, "/", "", false, true)
theme := utils.GetThemeFromEnv()
c.HTML(200, "home.html", gin.H{
"successMessage": "Images are now " + text,
"theme": c.MustGet("theme").(string),
"version": config.Version,
"theme": theme,
})
case "theme":
text := "dark"
if c.MustGet("theme").(string) == "dark" {
text = "light"
}
c.SetCookie("theme", text, 60*60*24*365*10, "/", "", false, true)
// get redirect url from query
redirectUrl := c.Query("redirect_url")
if redirectUrl == "" {
redirectUrl = os.Getenv("APP_URL")
}
if !strings.HasPrefix(redirectUrl, os.Getenv("APP_URL")) {
redirectUrl = os.Getenv("APP_URL")
}
c.Redirect(302, redirectUrl)
default:
c.String(400, "400 Bad Request")
}

View file

@ -29,36 +29,55 @@ var soSortValues = map[string]string{
}
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),
"version": config.Version,
})
return
}
params, err := parseAndValidateParameters(c)
if err != nil {
return
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 strings.Contains(params.Sub, ".") {
domain = params.Sub
} else if params.Sub != "" {
domain = fmt.Sprintf("%s.stackexchange.com", params.Sub)
if sub != "" {
domain = fmt.Sprintf("%s.stackexchange.com", sub)
}
soLink := fmt.Sprintf("https://%s/questions/%s/%s?answertab=%s", domain, questionId, params.QuestionTitle, params.SoSortValue)
soLink := fmt.Sprintf("https://%s/questions/%s/%s?answertab=%s", domain, questionId, questionTitle, soSortValue)
resp, err := fetchQuestionData(soLink)
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),
"version": config.Version,
})
return
}
defer resp.RawResponse.Body.Close()
if resp.StatusCode() != 200 {
c.HTML(500, "home.html", gin.H{
"errorMessage": fmt.Sprintf("Received a non-OK status code %d", resp.StatusCode()),
"errorMessage": "Received a non-OK status code",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
@ -72,203 +91,202 @@ func ViewQuestion(c *gin.Context) {
if err != nil {
c.HTML(500, "home.html", gin.H{
"errorMessage": "Unable to parse question data",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
}
newFilteredQuestion, err := extractQuestionData(doc, domain)
newFilteredQuestion := types.FilteredQuestion{}
questionTextParent := doc.Find("h1.fs-headline1")
questionText := questionTextParent.Children().First().Text()
newFilteredQuestion.Title = questionText
questionPostLayout := doc.Find("div.post-layout").First()
questionTags := utils.GetPostTags(questionPostLayout)
newFilteredQuestion.Tags = questionTags
questionBodyParent := doc.Find("div.s-prose")
questionBodyParentHTML, err := questionBodyParent.Html()
if err != nil {
c.HTML(500, "home.html", gin.H{
"errorMessage": "Failed to extract question data",
"errorMessage": "Unable to parse question body",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
}
answers, err := extractAnswersData(doc, domain)
if err != nil {
c.HTML(500, "home.html", gin.H{
"errorMessage": "Failed to extract answer data",
"version": config.Version,
})
return
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, domain, questionPostLayout)
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)
}
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
userDetails := questionMetadata.Find("div.user-details")
questionAuthor := ""
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) {
// if question has been edited, the author is the second element.
if isQuestionEdited {
if i == 1 {
questionAuthor = s.Text()
questionAuthorURL += s.AttrOr("href", "")
return
}
} else {
// otherwise it's the first element
if i == 0 {
questionAuthor = s.Text()
questionAuthorURL += s.AttrOr("href", "")
return
}
}
})
newFilteredQuestion.AuthorName = questionAuthor
newFilteredQuestion.AuthorURL = questionAuthorURL
answers := []types.FilteredAnswer{}
doc.Find("div.answer").Each(func(i int, s *goquery.Selection) {
newFilteredAnswer := types.FilteredAnswer{}
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 := html.EscapeString(voteCell.Find("div.js-vote-count").Text())
newFilteredAnswer.Upvotes = voteCount
newFilteredAnswer.IsAccepted = s.HasClass("accepted-answer")
answerFooter := s.Find("div.mt24")
answerAuthorURL := fmt.Sprintf("https://%s", domain)
answerAuthorName := ""
answerTimestamp := ""
answerFooter.Find("div.post-signature").Each(func(i int, s *goquery.Selection) {
answerAuthorDetails := s.Find("div.user-details")
if answerAuthorDetails.Length() > 0 {
questionAuthor := answerAuthorDetails.Find("a").First()
answerAuthorName = html.EscapeString(questionAuthor.Text())
answerAuthorURL += html.EscapeString(questionAuthor.AttrOr("href", ""))
}
answerTimestamp = html.EscapeString(s.Find("span.relativetime").Text())
})
answerId, _ := s.Attr("data-answerid")
newFilteredAnswer.ID = answerId
newFilteredAnswer.AuthorName = answerAuthorName
newFilteredAnswer.AuthorURL = answerAuthorURL
newFilteredAnswer.Timestamp = answerTimestamp
// 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)
}
comments = utils.FindAndReturnComments(answerBodyHTML, domain, postLayout)
newFilteredAnswer.Comments = comments
newFilteredAnswer.Body = template.HTML(utils.ReplaceImgTags(answerBodyHTML))
answers = append(answers, newFilteredAnswer)
})
imagePolicy := "'self' https:"
if c.MustGet("disable_images").(bool) {
imagePolicy = "'self'"
}
theme := utils.GetThemeFromEnv()
c.HTML(200, "question.html", gin.H{
"question": newFilteredQuestion,
"answers": answers,
"imagePolicy": imagePolicy,
"currentUrl": fmt.Sprintf("%s%s", os.Getenv("APP_URL"), c.Request.URL.Path),
"sortValue": params.SoSortValue,
"theme": c.MustGet("theme").(string),
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
"sortValue": sortValue,
"domain": domain,
"theme": theme,
})
}
type viewQuestionInputs struct {
QuestionID string
QuestionTitle string
SoSortValue string
Sub string
}
// parseAndValidateParameters consolidates the URL and query parameters into an easily-accessible struct.
func parseAndValidateParameters(c *gin.Context) (inputs viewQuestionInputs, err error) {
questionId := c.Param("id")
if _, err = strconv.Atoi(questionId); err != nil {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid question ID",
"version": config.Version,
})
return
}
inputs.QuestionID = questionId
sortValue := c.Query("sort_by")
if sortValue == "" {
sortValue = "votes"
}
soSortValue, ok := soSortValues[sortValue]
if !ok {
soSortValue = soSortValues["votes"]
}
inputs.SoSortValue = soSortValue
sub := c.Param("sub")
inputs.Sub = sub
return
}
// fetchQuestionData sends the request to StackOverflow.
func fetchQuestionData(soLink string) (resp *resty.Response, err error) {
client := resty.New()
resp, err = client.R().Get(soLink)
return
}
// extractQuestionData parses the HTML document and extracts question data.
func extractQuestionData(doc *goquery.Document, domain string) (question types.FilteredQuestion, err error) {
// Extract the question title.
questionTextParent := doc.Find("h1.fs-headline1").First()
question.Title = strings.TrimSpace(questionTextParent.Children().First().Text())
// Extract question tags.
questionTags := utils.GetPostTags(doc.Find("div.post-layout").First())
question.Tags = questionTags
// Extract and process the question body.
questionBodyParent := doc.Find("div.s-prose").First()
questionBodyParentHTML, err := questionBodyParent.Html()
if err != nil {
return question, err
}
question.Body = template.HTML(utils.ProcessHTMLBody(questionBodyParentHTML))
// Extract the shortened body description.
shortenedBody := strings.TrimSpace(questionBodyParent.Text())
shortenedBody = strings.ReplaceAll(shortenedBody, "\n", " ")
if len(shortenedBody) > 50 {
shortenedBody = shortenedBody[:50]
}
question.ShortenedBody = shortenedBody
// Extract question comments.
comments := utils.FindAndReturnComments(questionBodyParentHTML, domain, doc.Find("div.post-layout").First())
question.Comments = comments
// Extract question timestamp and author information.
questionCard := doc.Find("div.postcell").First()
extractMetadata(questionCard, &question, domain)
return
}
// extractMetadata extracts author and timestamp information from a given selection.
func extractMetadata(selection *goquery.Selection, question *types.FilteredQuestion, domain string) {
questionMetadata := selection.Find("div.user-info").First()
question.Timestamp = questionMetadata.Find("span.relativetime").First().Text()
questionAuthorURL := "https://" + domain
questionAuthor := selection.Find("div.post-signature.owner div.user-info div.user-details a").First()
question.AuthorName = questionAuthor.Text()
questionAuthorURL += questionAuthor.AttrOr("href", "")
question.AuthorURL = questionAuthorURL
// Determine if the question has been edited and update author details accordingly.
isQuestionEdited := selection.Find("a.js-gps-track").Text() == "edited"
if isQuestionEdited {
editedAuthor := questionMetadata.Find("a").Last()
question.AuthorName = editedAuthor.Text()
question.AuthorURL = "https://" + domain + editedAuthor.AttrOr("href", "")
}
}
// extractAnswersData parses the HTML document and extracts answers data.
func extractAnswersData(doc *goquery.Document, domain string) ([]types.FilteredAnswer, error) {
var answers []types.FilteredAnswer
// Iterate over each answer block.
doc.Find("div.answer").Each(func(i int, s *goquery.Selection) {
var answer types.FilteredAnswer
answer.ID = s.AttrOr("data-answerid", "")
postLayout := s.Find("div.post-layout").First()
// Extract upvotes.
voteCell := postLayout.Find("div.votecell").First()
voteCount := html.EscapeString(voteCell.Find("div.js-vote-count").Text())
answer.Upvotes = voteCount
// Check if the answer is accepted.
answer.IsAccepted = s.HasClass("accepted-answer")
// Extract answer body and process it.
answerCell := postLayout.Find("div.answercell").First()
answerBody := answerCell.Find("div.s-prose").First()
answerBodyHTML, _ := answerBody.Html()
// Process code blocks within the answer.
processedAnswerBody := utils.ProcessHTMLBody(answerBodyHTML)
answer.Body = template.HTML(processedAnswerBody)
answer.Comments = utils.FindAndReturnComments(answerBodyHTML, domain, postLayout)
// Extract author information and timestamp.
extractAnswerAuthorInfo(s, &answer, domain)
answers = append(answers, answer)
})
return answers, nil
}
// extractAnswerAuthorInfo extracts the author name, URL, and timestamp from an answer block.
// It directly mutates the answer.
func extractAnswerAuthorInfo(selection *goquery.Selection, answer *types.FilteredAnswer, domain string) {
authorDetails := selection.Find("div.post-signature").Last()
authorName := html.EscapeString(authorDetails.Find("div.user-details a").First().Text())
authorURL := "https://" + domain + authorDetails.Find("div.user-details a").AttrOr("href", "")
timestamp := html.EscapeString(authorDetails.Find("span.relativetime").Text())
answer.AuthorName = authorName
answer.AuthorURL = authorURL
answer.Timestamp = timestamp
}

View file

@ -1,56 +0,0 @@
package routes
import (
"fmt"
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
)
func RedirectShortenedOverflowURL(c *gin.Context) {
id := c.Param("id")
answerId := c.Param("answerId")
sub := c.Param("sub")
// fetch the stack overflow URL
client := resty.New()
client.SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
domain := "www.stackoverflow.com"
if strings.Contains(sub, ".") {
domain = sub
} else if sub != "" {
domain = fmt.Sprintf("%s.stackexchange.com", sub)
}
resp, err := client.R().Get(fmt.Sprintf("https://%s/a/%s/%s", domain, id, answerId))
if err != nil {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Unable to fetch stack overflow URL",
})
return
}
if resp.StatusCode() != 302 {
c.HTML(400, "home.html", gin.H{
"errorMessage": fmt.Sprintf("Unexpected HTTP status from origin: %d", resp.StatusCode()),
})
return
}
// get the redirect URL
location := resp.Header().Get("Location")
redirectPrefix := os.Getenv("APP_URL")
if sub != "" {
redirectPrefix += fmt.Sprintf("/exchange/%s", sub)
}
c.Redirect(302, fmt.Sprintf("%s%s", redirectPrefix, location))
}

View file

@ -1,10 +0,0 @@
package routes
import (
"anonymousoverflow/config"
"github.com/gin-gonic/gin"
)
func GetVersion(c *gin.Context) {
c.String(200, config.Version)
}

View file

@ -50,7 +50,7 @@ func FindAndReturnComments(inHtml, domain string, postLayout *goquery.Selection)
commentTimestamp := commentBody.Find("span.relativetime-clean").Text()
newFilteredComment := types.FilteredComment{
Text: template.HTML(ProcessHTMLBody(commentCopy)),
Text: template.HTML(commentCopy),
Timestamp: commentTimestamp,
AuthorName: commentAuthor.Text(),
AuthorURL: commentAuthorURL,

Some files were not shown because too many files have changed in this diff Show more