Compare commits

..

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

43 changed files with 390 additions and 1292 deletions

View File

@ -2,7 +2,6 @@ ADDRESS=0.0.0.0
PORT=3000 PORT=3000
FIBER_PREFORK=false FIBER_PREFORK=false
IMGUR_CLIENT_ID=546c25a59c58ad7 IMGUR_CLIENT_ID=546c25a59c58ad7
SECURE=true
# Instance privacy # Instance privacy
# For more information, see https://codeberg.org/librarian/librarian/wiki/Instance-privacy # For more information, see https://codeberg.org/librarian/librarian/wiki/Instance-privacy

1
.gitignore vendored
View File

@ -2,4 +2,3 @@
tmp tmp
static/app.css static/app.css
dist/ dist/
node_modules

View File

@ -1,7 +1,6 @@
version: 2
before: before:
hooks: hooks:
- pnpm run build - tailwindcss -i static/tailwind.css -o static/app.css -m
- go mod tidy - go mod tidy
project_name: rimgo project_name: rimgo
builds: builds:
@ -17,18 +16,16 @@ builds:
ldflags: ldflags:
- -X codeberg.org/rimgo/rimgo/pages.VersionInfo={{.Version}} - -X codeberg.org/rimgo/rimgo/pages.VersionInfo={{.Version}}
archives: archives:
- formats: tar.gz - format: tar.gz
name_template: >- name_template: >-
{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }} {{ .ProjectName }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}
format_overrides: format_overrides:
- goos: windows - goos: windows
formats: zip format: zip
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
kos: kos:
- repositories: - repository: codeberg.org/rimgo/rimgo
- codeberg.org/rimgo/rimgo
- codeberg.org/video-prize-ranch/rimgo
tags: tags:
- '{{.Version}}' - '{{.Version}}'
- latest - latest

View File

@ -3,14 +3,14 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETARCH ARG TARGETARCH
WORKDIR /src WORKDIR /src
RUN apk --no-cache add ca-certificates git nodejs pnpm RUN apk --no-cache add ca-certificates git nodejs npm
COPY . . COPY . .
RUN pnpm install && pnpm run build RUN npx tailwindcss -i static/tailwind.css -o static/app.css -m
RUN go mod download RUN go mod download
RUN GOOS=linux GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-X codeberg.org/rimgo/rimgo/pages.VersionInfo=$(date '+%Y-%m-%d')-$(git rev-list --abbrev-commit -1 HEAD)" RUN GOOS=linux GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-X codeberg.org/rimgo/rimgo/pages.VersionInfo=$(date '+%Y-%m-%d')-$(git rev-list --abbrev-commit -1 HEAD)"
FROM scratch AS bin FROM scratch as bin
WORKDIR /app WORKDIR /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

View File

@ -1,14 +1,7 @@
build: build:
pnpm run build tailwindcss -i static/tailwind.css -o static/app.css -m
CGO_ENABLED=0 go build -o rimgo -ldflags "-X codeberg.org/rimgo/rimgo/pages.VersionInfo=$(date '+%Y-%m-%d')-$(git rev-list --abbrev-commit -1 HEAD)" CGO_ENABLED=0 go build -o rimgo -ldflags "-X codeberg.org/rimgo/rimgo/pages.VersionInfo=$(date '+%Y-%m-%d')-$(git rev-list --abbrev-commit -1 HEAD)"
dev-css:
pnpm run watch
dev: dev:
go run github.com/air-verse/air@latest -c .air.toml tailwindcss -i static/tailwind.css -o static/app.css -m -w &
go run github.com/cosmtrek/air@latest -c .air.toml
tag-vpr:
podman pull codeberg.org/rimgo/rimgo:latest
podman image tag codeberg.org/rimgo/rimgo:latest codeberg.org/video-prize-ranch/rimgo:latest
podman push codeberg.org/video-prize-ranch/rimgo:latest

View File

@ -65,8 +65,58 @@ Stack Overflow: `https://i.stack.imgur.com/KnO3v.jpg?s=64&g=1` -> `https://rimgo
To automatically redirect Imgur links, see [Redirection](https://rimgo.codeberg.page/docs/usage/redirection/). To automatically redirect Imgur links, see [Redirection](https://rimgo.codeberg.page/docs/usage/redirection/).
## Instances ## Instances
Open an issue to have your instance listed here! See the rules for the instance list [here](https://rimgo.codeberg.page/docs/usage/instance-list-rules/).
Available at https://rimgo.codeberg.page/ or https://codeberg.org/rimgo/instances > For more details on instance privacy, see https://rimgo.codeberg.page/docs/usage/instance-privacy/
### Clearnet
| URL | Country | Provider | Privacy | Notes |
| :------------------------------------------------------------ | :----------- | :----------------------- | :-------------------- | :---- |
| [rimgo.pussthecat.org](https://rimgo.pussthecat.org) | 🇩🇪 DE | Hetzner | ⚠️ Data collected | |
| [rimgo.totaldarkness.net](https://rimgo.totaldarkness.net) | 🇨🇦 CA | Vultr | ✅ Data not collected | |
| [rimgo.bus-hit.me](https://rimgo.bus-hit.me) | 🇨🇦 CA | Oracle | ⚠️ Data collected | |
| [imgur.artemislena.eu](https://imgur.artemislena.eu) | 🇩🇪 DE | Vodafone Deutschland | ✅ Data not collected | Self-hosted, provider is ISP |
| [rimgo.vern.cc](https://rimgo.vern.cc) | 🇺🇸 US | OVHCloud | ✅ Data not collected | [Edited theme](https://git.vern.cc/root/modifications/src/branch/master/rimgo) |
| [rim.odyssey346.dev](https://rim.odyssey346.dev/) | 🇫🇷️ FR | Trolling Solutions (OVH) | ✅ Data not collected | |
| [i.habedieeh.re](https://i.habedieeh.re/) | 🇨🇦️ CA | Oracle Cloud | ✅ Data not collected | |
| [rimgo.hostux.net](https://rimgo.hostux.net/) | 🇫🇷️ FR | Gandi | ⚠️ Data collected | |
| [ri.zzls.xyz](https://ri.zzls.xyz/) | 🇨🇱 CL | TELEFÓNICA CHILE | ✅ Data not collected | Self-hosted, provider is ISP |
| [rimgo.lunar.icu](https://rimgo.marcopisco.com/) | 🇩🇪 DE | Cloudflare | ✅ Data not collected | |
| [imgur.010032.xyz](https://imgur.010032.xyz/) | 🇰🇷 KR | Oracle Cloud | ✅ Data not collected | |
| [rimgo.kling.gg](https://rimgo.kling.gg/) | 🇳🇱 NL | RamNode | ✅ Data not collected | |
| [i.01r.xyz](https://i.01r.xyz/) | 🇺🇸 US | Cloudflare | ✅ Data not collected | |
| [rimgo.projectsegfau.lt](https://rimgo.projectsegfau.lt/) | 🇫🇷 FR, 🇺🇸 US, 🇮🇳 IN | See below | ✅ Data not collected | |
| [rimgo.eu.projectsegfau.lt](https://rimgo.eu.projectsegfau.lt/) | 🇫🇷 FR | Orange S.A. | ✅ Data not collected | |
| [rimgo.us.projectsegfau.lt](https://rimgo.us.projectsegfau.lt/) | 🇺🇸 US | Racknerd | ✅ Data not collected | |
| [rimgo.in.projectsegfau.lt](https://rimgo.in.projectsegfau.lt/) | 🇮🇳 IN | Airtel | ✅ Data not collected | |
| [rimgo.whateveritworks.org](https://rimgo.whateveritworks.org/) | 🇩🇪 DE | Cloudflare | ✅ Data not collected | |
| [rimgo.nohost.network](https://rimgo.nohost.network/) | 🇲🇽 MX | Telmex | ✅ Data not collected | |
| [rimgo.catsarch.com](https://rimgo.catsarch.com/) | 🇺🇸 US | Comcast | ✅ Data not collected | Self-hosted, provider is ISP |
| [rimgo.frontendfriendly.xyz](https://rimgo.frontendfriendly.xyz/) | 🇩🇪 DE | Hetzner | ⚠️ Data collected | |
### Tor
| URL | Privacy | Notes |
| :-- | :------ | :----------------------- |
| [rimgo.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion](http://rimgo.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion) | ✅ Data not collected | Onion of rimgo.vern.cc |
| [imgur.lpoaj7z2zkajuhgnlltpeqh3zyq7wk2iyeggqaduhgxhyajtdt2j7wad.onion](http://imgur.lpoaj7z2zkajuhgnlltpeqh3zyq7wk2iyeggqaduhgxhyajtdt2j7wad.onion) | ✅ Data not collected | Onion of imgur.artemislena.eu |
| [rim.odysfvr23q5wgt7i456o5t3trw2cw5dgn56vbjfbq2m7xsc5vqbqpcyd.onion](http://rim.odysfvr23q5wgt7i456o5t3trw2cw5dgn56vbjfbq2m7xsc5vqbqpcyd.onion) | ⚠️ Data collected | |
| [tdp6uqjtmok723suum5ms3jbquht6d7dssug4cgcxhfniatb25gcipad.onion](http://tdp6uqjtmok723suum5ms3jbquht6d7dssug4cgcxhfniatb25gcipad.onion) | ✅ Data not collected | Onion of rimgo.privacytools.io |
| [i.habeehrhadazsw3izbrbilqajalfyqqln54mrja3iwpqxgcuxnus7eid.onion](http://i.habeehrhadazsw3izbrbilqajalfyqqln54mrja3iwpqxgcuxnus7eid.onion/) | ✅ Data not collected | Onion of i.habedieeh.re |
| [rimgo.zzlsghu6mvvwyy75mvga6gaf4znbp3erk5xwfzedb4gg6qqh2j6rlvid.onion](http://rimgo.zzlsghu6mvvwyy75mvga6gaf4znbp3erk5xwfzedb4gg6qqh2j6rlvid.onion/) | ✅ Data not collected | Onion of ri.zzls.xyz |
| [tdn7zoxctmsopey77mp4eg2gazaudyhgbuyytf4zpk5u7lknlxlgbnid.onion/](http://tdn7zoxctmsopey77mp4eg2gazaudyhgbuyytf4zpk5u7lknlxlgbnid.onion/) | ✅ Data not collected | Onion of rimgo.kling.gg |
| [rimgo.pjsfkvpxlinjamtawaksbnnaqs2fc2mtvmozrzckxh7f3kis6yea25ad.onion](http://rimgo.pjsfkvpxlinjamtawaksbnnaqs2fc2mtvmozrzckxh7f3kis6yea25ad.onion/) | ✅ Data not collected | Onion of rimgo.eu.projectsegfau.lt |
### I2P
| URL | Privacy | Notes |
| :-- | :------ | :----------------------- |
| [rimgo.i2p](http://rimgo.i2p) | ✅ Data not collected | i.habedieeh.re on I2P |
| [rimgov7l2tqyrm5txrtvhtnfyrzkc5d7ipafofavchbnnyog4r3q.b32.i2p](http://rimgov7l2tqyrm5txrtvhtnfyrzkc5d7ipafofavchbnnyog4r3q.b32.i2p) | ✅ Data not collected | Same as rimgo.i2p |
| [rimgo.zzls.i2p](http://rimgo.zzls.i2p) | ✅ Data not collected | ri.zzls.xyz on I2P |
| [p57356k2xwhxrg2lxrjajcftkrptv4zejeeblzfgkcvpzuetkz2a.b32.i2p](http://p57356k2xwhxrg2lxrjajcftkrptv4zejeeblzfgkcvpzuetkz2a.b32.i2p) | ✅ Data not collected | Same as rimgo.zzls.i2p |
| [ovzamsts5czfx3jasbbhbccyyl2z7qmdngtlqxdh4oi7abhdz3ia.b32.i2p](http://ovzamsts5czfx3jasbbhbccyyl2z7qmdngtlqxdh4oi7abhdz3ia.b32.i2p) | ✅ Data not collected | rimgo.kling.gg on I2P |
## Install ## Install
@ -77,10 +127,7 @@ See [Install](https://rimgo.codeberg.page/docs/getting-started/install/).
See [Configuration](https://rimgo.codeberg.page/docs/usage/configuration/). See [Configuration](https://rimgo.codeberg.page/docs/usage/configuration/).
## Contributing ## Contributing
Pull requests are welcome! If you have any questions or bug reports, open an [issue](https://codeberg.org/rimgo/rimgo/issues/new). Please remember to follow our [Code of Conduct](https://rimgo.codeberg.page/docs/code-of-conduct/)! Pull requests are welcome! If you have any questions or bug reports, open an [issue](https://codeberg.org/rimgo/rimgo/issues/new).
## License ## License
This software is released under the AGPL-3.0 license. If you make any modifications to the code and distribute it (including use on a network server), you must publicly distribute your changes and release them under the AGPL-3.0. This software is released under the AGPL-3.0 license. If you make any modifications to the code and distribute it (including use on a network server), you must publicly distribute your changes and release them under the AGPL-3.0.
## Legal notice
rimgo does not allow uploads or host any content, media. All content on any rimgo instances is from Imgur™. Imgur is a trademark of Imgur, Inc. Any issues with content on rimgo should be be reported to Imgur. rimgo is not affiliated with Imgur, Inc.

View File

@ -1,10 +1,10 @@
package api package api
import ( import (
"io" "io/ioutil"
"net/http" "net/http"
"net/url"
"strings" "strings"
"sync"
"github.com/patrickmn/go-cache" "github.com/patrickmn/go-cache"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@ -21,9 +21,6 @@ type Tag struct {
} }
func (client *Client) FetchTag(tag string, sort string, page string) (Tag, error) { func (client *Client) FetchTag(tag string, sort string, page string) (Tag, error) {
// Dots are automatically removed on Imgur, so more cache hits
tag = strings.ReplaceAll(tag, ".", "")
cacheData, found := client.Cache.Get(tag + sort + page + "-tag") cacheData, found := client.Cache.Get(tag + sort + page + "-tag")
if found { if found {
return cacheData.(Tag), nil return cacheData.(Tag), nil
@ -60,25 +57,25 @@ func (client *Client) FetchTag(tag string, sort string, page string) (Tag, error
return Tag{}, err return Tag{}, err
} }
body, err := io.ReadAll(res.Body) body, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
return Tag{}, err return Tag{}, err
} }
data := gjson.Parse(string(body)) data := gjson.Parse(string(body))
wg := sync.WaitGroup{}
posts := make([]Submission, 0) posts := make([]Submission, 0)
data.Get("posts").ForEach( data.Get("posts").ForEach(
func(key, value gjson.Result) bool { func(key, value gjson.Result) bool {
url, _ := url.Parse(strings.ReplaceAll(value.Get("url").String(), "https://imgur.com", "")) wg.Add(1)
q := url.Query()
q.Add("tag", tag+"."+sort+"."+page+"."+key.String())
url.RawQuery = q.Encode()
go func() {
defer wg.Done()
posts = append(posts, Submission{ posts = append(posts, Submission{
Id: value.Get("id").String(), Id: value.Get("id").String(),
Title: value.Get("title").String(), Title: value.Get("title").String(),
Link: url.String(), Link: strings.ReplaceAll(value.Get("url").String(), "https://imgur.com", ""),
Cover: Media{ Cover: Media{
Id: value.Get("cover_id").String(), Id: value.Get("cover_id").String(),
Type: value.Get("cover.type").String(), Type: value.Get("cover.type").String(),
@ -91,11 +88,14 @@ func (client *Client) FetchTag(tag string, sort string, page string) (Tag, error
Views: value.Get("view_count").Int(), Views: value.Get("view_count").Int(),
IsAlbum: value.Get("is_album").Bool(), IsAlbum: value.Get("is_album").Bool(),
}) })
}()
return true return true
}, },
) )
wg.Wait()
tagData := Tag{ tagData := Tag{
Tag: tag, Tag: tag,
Display: data.Get("display").String(), Display: data.Get("display").String(),
@ -105,6 +105,6 @@ func (client *Client) FetchTag(tag string, sort string, page string) (Tag, error
Background: "/" + data.Get("background_id").String() + ".webp", Background: "/" + data.Get("background_id").String() + ".webp",
} }
client.Cache.Set(tag+sort+page+"-tag", tagData, 4*cache.DefaultExpiration) client.Cache.Set(tag + sort + page + "-tag", tagData, cache.DefaultExpiration)
return tagData, nil return tagData, nil
} }

View File

@ -70,7 +70,7 @@ func (client *Client) FetchUser(username string) (User, error) {
} }
func (client *Client) FetchSubmissions(username string, sort string, page string) ([]Submission, error) { func (client *Client) FetchSubmissions(username string, sort string, page string) ([]Submission, error) {
cacheData, found := client.Cache.Get(username + "-submissions-" + sort + page) cacheData, found := client.Cache.Get(username + "-submissions")
if found { if found {
return cacheData.([]Submission), nil return cacheData.([]Submission), nil
} }
@ -98,56 +98,7 @@ func (client *Client) FetchSubmissions(username string, sort string, page string
) )
wg.Wait() wg.Wait()
client.Cache.Set(username+"-submissions-"+sort+page, submissions, 15*time.Minute) client.Cache.Set(username+"-submissions", submissions, 15*time.Minute)
return submissions, nil
}
func (client *Client) FetchUserFavorites(username string, sort string, page string) ([]Submission, error) {
cacheData, found := client.Cache.Get(username + "-favorites-" + sort + page)
if found {
return cacheData.([]Submission), nil
}
req, err := http.NewRequest("GET", "https://api.imgur.com/3/account/"+username+"/gallery_favorites/"+page+"/"+sort, nil)
if err != nil {
return []Submission{}, err
}
utils.SetReqHeaders(req)
q := req.URL.Query()
q.Add("client_id", client.ClientID)
req.URL.RawQuery = q.Encode()
res, err := http.DefaultClient.Do(req)
if err != nil {
return []Submission{}, err
}
body, err := io.ReadAll(res.Body)
if err != nil {
return []Submission{}, err
}
data := gjson.Parse(string(body))
submissions := []Submission{}
wg := sync.WaitGroup{}
data.Get("data").ForEach(
func(key, value gjson.Result) bool {
wg.Add(1)
go func() {
defer wg.Done()
submissions = append(submissions, parseSubmission(value))
}()
return true
},
)
wg.Wait()
client.Cache.Set(username+"-favorites-"+sort+page, submissions, 15*time.Minute)
return submissions, nil return submissions, nil
} }

View File

@ -1,3 +1,5 @@
version: '3'
services: services:
rimgo: rimgo:
image: codeberg.org/rimgo/rimgo # Official image image: codeberg.org/rimgo/rimgo # Official image

50
go.mod
View File

@ -1,45 +1,41 @@
module codeberg.org/rimgo/rimgo module codeberg.org/rimgo/rimgo
go 1.23 go 1.17
toolchain go1.23.5
require ( require (
github.com/PuerkitoBio/goquery v1.10.1 github.com/PuerkitoBio/goquery v1.8.1
github.com/aymerick/raymond v2.0.2+incompatible
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/gofiber/fiber/v2 v2.52.6 github.com/gofiber/fiber/v2 v2.48.0
github.com/gofiber/template/handlebars/v2 v2.1.11 github.com/gofiber/template/handlebars/v2 v2.1.4
github.com/gorilla/feeds v1.2.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mailgun/raymond/v2 v2.0.48 github.com/microcosm-cc/bluemonday v1.0.25
github.com/microcosm-cc/bluemonday v1.0.27
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/tidwall/gjson v1.18.0 github.com/tidwall/gjson v1.14.4
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3
) )
require ( require (
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/template v1.8.2 // indirect
github.com/gofiber/utils v1.1.0 // indirect github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.16.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/philhofer/fwd v1.1.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tinylib/msgp v1.2.5 // indirect github.com/tinylib/msgp v1.1.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.58.0 // indirect github.com/valyala/fasthttp v1.48.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.11.0 // indirect
) )

190
go.sum
View File

@ -1,162 +1,148 @@
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= 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/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.48.0 h1:cRVMCb9aUJDsyHxGFLwz/sGzDggdailZZyptU9F9cU0=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= github.com/gofiber/fiber/v2 v2.48.0/go.mod h1:xqJgfqrc23FJuqGOW6DVgi3HyZEm2Mn9pRqUb2kHSX8=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
github.com/gofiber/template/handlebars/v2 v2.1.11 h1:pgPF+DKuIvCl3z/Kj1u6VA/8hLBljH2Rg6LwVFBm7aM= github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/handlebars/v2 v2.1.11/go.mod h1:AbKfYOgH+ngxaYXtLzafy4AKLAQ2NrJYbvtWaOX82I4= github.com/gofiber/template/handlebars/v2 v2.1.4 h1:m/GwEnzv5bpifOg9BrpCIzkhD2GU10E5oZjI8NdDbYY=
github.com/gofiber/template/handlebars/v2 v2.1.4/go.mod h1:bb6ip6ZEgBqKSdZcbnFLlfL8PcCRslnX6WgpcxVBiTE=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc=
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 h1:1Coh5BsUBlXoEJmIEaNzVAWrtg9k7/eJzailMQr1grw= gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 h1:1Coh5BsUBlXoEJmIEaNzVAWrtg9k7/eJzailMQr1grw=
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8= gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210615035016-665e8c7367d1/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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-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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=

View File

@ -28,6 +28,21 @@
], ],
"cloudflare": false "cloudflare": false
}, },
{
"url": "https://rimgo.vern.cc",
"onion": "http://rimgo.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion",
"countries": [
"ca"
],
"cloudflare": false
},
{
"url": "https://rim.odyssey346.dev",
"countries": [
"fr"
],
"cloudflare": false
},
{ {
"url": "https://imgur.010032.xyz", "url": "https://imgur.010032.xyz",
"countries": [ "countries": [
@ -82,6 +97,13 @@
], ],
"cloudflare": false "cloudflare": false
}, },
{
"url": "https://i.01r.xyz",
"countries": [
"us"
],
"cloudflare": true
},
{ {
"url": "https://rimgo.eu.projectsegfau.lt", "url": "https://rimgo.eu.projectsegfau.lt",
"countries": [ "countries": [
@ -103,13 +125,6 @@
], ],
"cloudflare": false "cloudflare": false
}, },
{
"url": "https://rimgo.fascinated.cc",
"countries": [
"us"
],
"cloudflare": false
},
{ {
"url": "https://rimgo.whateveritworks.org", "url": "https://rimgo.whateveritworks.org",
"countries": [ "countries": [
@ -137,26 +152,5 @@
"de" "de"
], ],
"cloudflare": false "cloudflare": false
},
{
"url": "https://rimgo.drgns.space",
"countries": [
"us"
],
"cloudflare": false
},
{
"url": "https://rimgo.quantenzitrone.eu",
"countries": [
"cz"
],
"cloudflare": false
},
{
"url": "https://rimgo.frylo.net",
"countries": [
"nl"
],
"cloudflare": false
} }
] ]

31
main.go
View File

@ -11,7 +11,7 @@ import (
"codeberg.org/rimgo/rimgo/static" "codeberg.org/rimgo/rimgo/static"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"codeberg.org/rimgo/rimgo/views" "codeberg.org/rimgo/rimgo/views"
"github.com/mailgun/raymond/v2" "github.com/aymerick/raymond"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cache" "github.com/gofiber/fiber/v2/middleware/cache"
"github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/fiber/v2/middleware/filesystem"
@ -52,7 +52,14 @@ func main() {
code = e.Code code = e.Code
} }
return utils.RenderError(ctx, code) err = ctx.Status(code).Render("errors/error", fiber.Map{
"err": err,
})
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return nil
}, },
}) })
@ -65,31 +72,22 @@ func main() {
if os.Getenv("ENV") == "dev" { if os.Getenv("ENV") == "dev" {
app.Use("/static", filesystem.New(filesystem.Config{ app.Use("/static", filesystem.New(filesystem.Config{
MaxAge: 2592000,
Root: http.Dir("./static"), Root: http.Dir("./static"),
})) }))
app.Get("/errors/429", func(c *fiber.Ctx) error { app.Get("/errors/429", func(c *fiber.Ctx) error {
return c.Render("errors/429", nil) return c.Render("errors/429", nil)
}) })
app.Get("/errors/429/img", func(c *fiber.Ctx) error {
return c.Redirect("/static/img/error-429.png")
})
app.Get("/errors/404", func(c *fiber.Ctx) error { app.Get("/errors/404", func(c *fiber.Ctx) error {
return c.Render("errors/404", nil) return c.Render("errors/404", nil)
}) })
app.Get("/errors/404/img", func(c *fiber.Ctx) error {
return c.Redirect("/static/img/error-404.png")
})
app.Get("/errors/error", func(c *fiber.Ctx) error { app.Get("/errors/error", func(c *fiber.Ctx) error {
return c.Render("errors/error", fiber.Map{ return c.Render("errors/error", fiber.Map{
"err": "Test error", "err": "Test error",
}) })
}) })
app.Get("/errors/error/img", func(c *fiber.Ctx) error {
return c.Redirect("/static/img/error-generic.png")
})
} else { } else {
app.Use("/static", filesystem.New(filesystem.Config{ app.Use("/static", filesystem.New(filesystem.Config{
MaxAge: 2592000,
Root: http.FS(static.GetFiles()), Root: http.FS(static.GetFiles()),
})) }))
app.Use(cache.New(cache.Config{ app.Use(cache.New(cache.Config{
@ -118,17 +116,13 @@ func main() {
app.Get("/about", pages.HandleAbout) app.Get("/about", pages.HandleAbout)
app.Get("/privacy", pages.HandlePrivacy) app.Get("/privacy", pages.HandlePrivacy)
app.Get("/search", pages.HandleSearch) app.Get("/search", pages.HandleSearch)
app.Get("/trending.:type", pages.HandleTrendingRSS)
app.Get("/trending", pages.HandleTrending) app.Get("/trending", pages.HandleTrending)
app.Get("/a/:postID", pages.HandlePost) app.Get("/a/:postID", pages.HandlePost)
app.Get("/a/:postID/embed", pages.HandleEmbed) app.Get("/a/:postID/embed", pages.HandleEmbed)
app.Get("/t/:tag.:type", pages.HandleTagRSS)
app.Get("/t/:tag", pages.HandleTag) app.Get("/t/:tag", pages.HandleTag)
app.Get("/t/:tag/:postID", pages.HandlePost) app.Get("/t/:tag/:postID", pages.HandlePost)
app.Get("/r/:sub/:postID", pages.HandlePost) app.Get("/r/:sub/:postID", pages.HandlePost)
app.Get("/user/:userID.:type", pages.HandleUserRSS)
app.Get("/user/:userID", pages.HandleUser) app.Get("/user/:userID", pages.HandleUser)
app.Get("/user/:userID/favorites", pages.HandleUserFavorites)
app.Get("/user/:userID/comments", pages.HandleUserComments) app.Get("/user/:userID/comments", pages.HandleUserComments)
app.Get("/user/:userID/cover", pages.HandleUserCover) app.Get("/user/:userID/cover", pages.HandleUserCover)
app.Get("/user/:userID/avatar", pages.HandleUserAvatar) app.Get("/user/:userID/avatar", pages.HandleUserAvatar)
@ -140,8 +134,5 @@ func main() {
app.Get("/:postID", pages.HandlePost) app.Get("/:postID", pages.HandlePost)
app.Get("/:postID/embed", pages.HandleEmbed) app.Get("/:postID/embed", pages.HandleEmbed)
err := app.Listen(utils.Config.Addr + ":" + utils.Config.Port) app.Listen(utils.Config.Addr + ":" + utils.Config.Port)
if err != nil {
fmt.Println(err)
}
} }

View File

@ -1,10 +0,0 @@
{
"devDependencies": {
"@tailwindcss/cli": "^4.1.5",
"tailwindcss": "^4.1.5"
},
"scripts": {
"build": "tailwindcss -i static/tailwind.css -o static/app.css",
"watch": "tailwindcss -i static/tailwind.css -o static/app.css --watch"
}
}

View File

@ -23,10 +23,10 @@ func HandleEmbed(c *fiber.Ctx) error {
post, err = ApiClient.FetchMedia(c.Params("postID")) post, err = ApiClient.FetchMedia(c.Params("postID"))
} }
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429) return c.Status(429).Render("errors/429", nil)
} }
if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") { if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") {
return utils.RenderError(c, 404) return c.Status(404).Render("errors/404", nil)
} }
if err != nil { if err != nil {
return err return err

View File

@ -11,7 +11,6 @@ import (
func HandleMedia(c *fiber.Ctx) error { func HandleMedia(c *fiber.Ctx) error {
c.Set("Cache-Control", "public,max-age=31557600") c.Set("Cache-Control", "public,max-age=31557600")
c.Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'")
if strings.HasPrefix(c.Path(), "/stack") { if strings.HasPrefix(c.Path(), "/stack") {
return handleMedia(c, "https://i.stack.imgur.com/" + strings.ReplaceAll(c.Params("baseName"), "stack/", "") + "." + c.Params("extension")) return handleMedia(c, "https://i.stack.imgur.com/" + strings.ReplaceAll(c.Params("baseName"), "stack/", "") + "." + c.Params("extension"))
} else { } else {
@ -21,15 +20,13 @@ func HandleMedia(c *fiber.Ctx) error {
func HandleUserCover(c *fiber.Ctx) error { func HandleUserCover(c *fiber.Ctx) error {
c.Set("Cache-Control", "public,max-age=604800") c.Set("Cache-Control", "public,max-age=604800")
c.Set("Content-Security-Policy", "default-src 'none'")
return handleMedia(c, "https://imgur.com/user/" + c.Params("userID") + "/cover?maxwidth=2560") return handleMedia(c, "https://imgur.com/user/" + c.Params("userID") + "/cover?maxwidth=2560")
} };
func HandleUserAvatar(c *fiber.Ctx) error { func HandleUserAvatar(c *fiber.Ctx) error {
c.Set("Cache-Control", "public,max-age=604800") c.Set("Cache-Control", "public,max-age=604800")
c.Set("Content-Security-Policy", "default-src 'none'")
return handleMedia(c, "https://imgur.com/user/" + c.Params("userID") + "/avatar") return handleMedia(c, "https://imgur.com/user/" + c.Params("userID") + "/avatar")
} };
func handleMedia(c *fiber.Ctx, url string) error { func handleMedia(c *fiber.Ctx, url string) error {
utils.SetHeaders(c) utils.SetHeaders(c)
@ -60,14 +57,19 @@ func handleMedia(c *fiber.Ctx, url string) error {
return err return err
} }
if res.StatusCode == 404 || strings.Contains(res.Request.URL.String(), "error/404") { c.Status(res.StatusCode)
return utils.RenderError(c, 404) if res.StatusCode == 404 {
return c.Render("errors/404", fiber.Map{
"path": c.Path(),
})
} else if res.StatusCode == 429 { } else if res.StatusCode == 429 {
return utils.RenderError(c, 429) return c.Render("errors/429", fiber.Map{
"path": c.Path(),
})
} }
c.Set("Accept-Ranges", "bytes") c.Set("Accept-Ranges", "bytes")
c.Set("Content-Type", res.Header.Get("Content-Type")) c.Set("Content-Type", res.Header.Get("Content-Type"));
c.Set("Content-Length", res.Header.Get("Content-Length")) c.Set("Content-Length", res.Header.Get("Content-Length"))
if res.Header.Get("Content-Range") != "" { if res.Header.Get("Content-Range") != "" {
c.Set("Content-Range", res.Header.Get("Content-Range")) c.Set("Content-Range", res.Header.Get("Content-Range"))

View File

@ -3,7 +3,6 @@ package pages
import ( import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"strconv"
"strings" "strings"
"codeberg.org/rimgo/rimgo/api" "codeberg.org/rimgo/rimgo/api"
@ -11,53 +10,28 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
// Cursed function
func nextInTag(client *api.Client, tagname, sort, page, I string) string {
i, err := strconv.Atoi(I)
if err != nil || i < 0 {
return ""
}
tag, err := client.FetchTag(tagname, sort, page)
if err != nil {
return ""
}
if i >= len(tag.Posts)-1 {
pageNumber, _ := strconv.Atoi(page)
tagn, err := client.FetchTag(tagname, sort, strconv.Itoa(pageNumber+1))
// Check length - Imgur will not return an error if there are no more posts and you request the next page
if err != nil || len(tagn.Posts) < 1 {
return ""
}
return tagn.Posts[0].Link
}
return tag.Posts[i+1].Link
}
func HandlePost(c *fiber.Ctx) error { func HandlePost(c *fiber.Ctx) error {
utils.SetHeaders(c) utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY") c.Set("X-Frame-Options", "DENY")
postId := c.Params("postID")
if strings.Contains(postId, "-") {
postId = postId[len(postId)-7:]
}
post, err := api.Album{}, error(nil) post, err := api.Album{}, error(nil)
switch { switch {
case strings.HasPrefix(c.Path(), "/a"): case strings.HasPrefix(c.Path(), "/a"):
post, err = ApiClient.FetchAlbum(postId) post, err = ApiClient.FetchAlbum(c.Params("postID"))
case strings.HasPrefix(c.Path(), "/gallery"): case strings.HasPrefix(c.Path(), "/gallery"):
post, err = ApiClient.FetchPosts(postId) post, err = ApiClient.FetchPosts(c.Params("postID"))
case strings.HasPrefix(c.Path(), "/t"): case strings.HasPrefix(c.Path(), "/t"):
post, err = ApiClient.FetchPosts(postId) post, err = ApiClient.FetchPosts(c.Params("postID"))
default: default:
post, err = ApiClient.FetchMedia(postId) post, err = ApiClient.FetchMedia(c.Params("postID"))
} }
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429) return c.Status(429).Render("errors/429", fiber.Map{
"path": c.Path(),
})
} }
if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") { if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") {
return utils.RenderError(c, 404) return c.Status(404).Render("errors/404", nil)
} }
if err != nil { if err != nil {
return err return err
@ -66,7 +40,7 @@ func HandlePost(c *fiber.Ctx) error {
comments := []api.Comment{} comments := []api.Comment{}
if post.SharedWithCommunity { if post.SharedWithCommunity {
c.Set("Cache-Control", "public,max-age=604800") c.Set("Cache-Control", "public,max-age=604800")
comments, err = ApiClient.FetchComments(postId) comments, err = ApiClient.FetchComments(c.Params("postID"))
if err != nil { if err != nil {
return err return err
} }
@ -84,16 +58,8 @@ func HandlePost(c *fiber.Ctx) error {
} }
c.Set("Content-Security-Policy", csp) c.Set("Content-Security-Policy", csp)
var next string
tagParam := strings.Split(c.Query("tag"), ".")
if len(tagParam) == 4 {
tag, sort, page, index := tagParam[0], tagParam[1], tagParam[2], tagParam[3]
next = nextInTag(ApiClient, tag, sort, page, index)
}
return c.Render("post", fiber.Map{ return c.Render("post", fiber.Map{
"post": post, "post": post,
"next": next,
"comments": comments, "comments": comments,
"nonce": nonce, "nonce": nonce,
}) })

View File

@ -9,7 +9,6 @@ import (
func HandlePrivacy(c *fiber.Ctx) error { func HandlePrivacy(c *fiber.Ctx) error {
utils.SetHeaders(c) utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY") c.Set("X-Frame-Options", "DENY")
c.Set("Content-Security-Policy", "default-src 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
return c.Render("privacy", fiber.Map{ return c.Render("privacy", fiber.Map{
"config": utils.Config, "config": utils.Config,

View File

@ -1,136 +0,0 @@
package pages
import (
"time"
"codeberg.org/rimgo/rimgo/api"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
"github.com/gorilla/feeds"
)
func HandleTagRSS(c *fiber.Ctx) error {
utils.SetHeaders(c)
tag, err := ApiClient.FetchTag(c.Params("tag"), c.Query("sort"), "1")
if err != nil && err.Error() == "ratelimited by imgur" {
return c.Status(429).SendString("rate limited by imgur")
}
if err != nil {
return err
}
if tag.Display == "" {
return c.Status(404).SendString("tag not found")
}
instance := utils.GetInstanceUrl(c)
feed := &feeds.Feed{
Title: tag.Display + " on Imgur",
Link: &feeds.Link{Href: instance + "/t/" + c.Params("tag")},
Created: time.Now(),
}
return handleFeed(c, instance, feed, tag.Posts)
}
func HandleTrendingRSS(c *fiber.Ctx) error {
utils.SetHeaders(c)
section := c.Query("section")
switch section {
case "hot", "new", "top":
default:
section = "hot"
}
sort := c.Query("sort")
switch sort {
case "newest", "best", "popular":
default:
sort = "popular"
}
results, err := ApiClient.FetchTrending(section, sort, "1")
if err != nil {
return err
}
instance := utils.GetInstanceUrl(c)
feed := &feeds.Feed{
Title: "Trending on Imgur",
Link: &feeds.Link{Href: instance + "/trending"},
Created: time.Now(),
}
return handleFeed(c, instance, feed, results)
}
func HandleUserRSS(c *fiber.Ctx) error {
utils.SetHeaders(c)
user := c.Params("userID")
submissions, err := ApiClient.FetchSubmissions(user, "newest", "1")
if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429)
return utils.RenderError(c, 429)
}
if err != nil {
return err
}
instance := utils.GetInstanceUrl(c)
feed := &feeds.Feed{
Title: user + " on Imgur",
Link: &feeds.Link{Href: instance + "/user/" + user},
Created: time.Now(),
}
return handleFeed(c, instance, feed, submissions)
}
func handleFeed(c *fiber.Ctx, instance string, feed *feeds.Feed, posts []api.Submission) error {
feed.Items = []*feeds.Item{}
for _, post := range posts {
link := instance + post.Link
item := &feeds.Item{
Title: post.Title,
Link: &feeds.Link{Href: link},
Description: "<a href=\"" + link + "\"><img width=\"480\" src=\"" + instance + "/" + post.Cover.Id + ".jpeg" + "\"></a>",
}
if post.Cover.Type == "video" {
item.Description = "🎞️ Video<br><br>" + item.Description
}
feed.Items = append(feed.Items, item)
}
c.Type(c.Params("type"))
switch c.Params("type") {
case "atom":
body, err := feed.ToAtom()
if err != nil {
return err
}
return c.SendString(body)
case "json":
body, err := feed.ToJSON()
if err != nil {
return err
}
return c.JSON(body)
case "rss":
body, err := feed.ToRss()
if err != nil {
return err
}
return c.SendString(body)
default:
return c.Status(400).SendString("invalid type")
}
}

View File

@ -29,6 +29,11 @@ func HandleSearch(c *fiber.Ctx) error {
pageNumber = 0 pageNumber = 0
} }
displayPrevPage := true
if page == "0" {
displayPrevPage = false
}
results, err := ApiClient.Search(query, page) results, err := ApiClient.Search(query, page)
if err != nil { if err != nil {
return err return err
@ -37,7 +42,8 @@ func HandleSearch(c *fiber.Ctx) error {
return c.Render("search", fiber.Map{ return c.Render("search", fiber.Map{
"query": query, "query": query,
"results": results, "results": results,
"page": pageNumber, "page": pageNumber + 1,
"displayPrev": displayPrevPage,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,
"prevPage": pageNumber - 1, "prevPage": pageNumber - 1,
}) })

View File

@ -23,20 +23,28 @@ func HandleTag(c *fiber.Ctx) error {
pageNumber = 0 pageNumber = 0
} }
displayPrevPage := true
if page == "1" {
displayPrevPage = false
}
tag, err := ApiClient.FetchTag(c.Params("tag"), c.Query("sort"), page) tag, err := ApiClient.FetchTag(c.Params("tag"), c.Query("sort"), page)
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429) return c.Status(429).Render("errors/429", fiber.Map{
"path": c.Path(),
})
} }
if err != nil { if err != nil {
return err return err
} }
if tag.Display == "" { if tag.Display == "" {
return utils.RenderError(c, 404) return c.Status(404).Render("errors/404", nil)
} }
return c.Render("tag", fiber.Map{ return c.Render("tag", fiber.Map{
"tag": tag, "tag": tag,
"page": page, "page": page,
"displayPrev": displayPrevPage,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,
"prevPage": pageNumber - 1, "prevPage": pageNumber - 1,
}) })

View File

@ -36,6 +36,11 @@ func HandleTrending(c *fiber.Ctx) error {
sort = "popular" sort = "popular"
} }
displayPrevPage := true
if page == "1" {
displayPrevPage = false
}
results, err := ApiClient.FetchTrending(section, sort, page) results, err := ApiClient.FetchTrending(section, sort, page)
if err != nil { if err != nil {
return err return err
@ -46,6 +51,7 @@ func HandleTrending(c *fiber.Ctx) error {
"section": section, "section": section,
"sort": sort, "sort": sort,
"page": pageNumber, "page": pageNumber,
"displayPrev": displayPrevPage,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,
"prevPage": pageNumber - 1, "prevPage": pageNumber - 1,
}) })

View File

@ -25,19 +25,23 @@ func HandleUser(c *fiber.Ctx) error {
user, err := ApiClient.FetchUser(c.Params("userID")) user, err := ApiClient.FetchUser(c.Params("userID"))
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429) return c.Status(429).Render("errors/429", fiber.Map{
"path": c.Path(),
})
} }
if err != nil { if err != nil {
return err return err
} }
if user.Username == "" { if user.Username == "" {
return utils.RenderError(c, 404) return c.Status(404).Render("errors/404", nil)
} }
submissions, err := ApiClient.FetchSubmissions(c.Params("userID"), "newest", page) submissions, err := ApiClient.FetchSubmissions(c.Params("userID"), "newest", page)
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429) c.Status(429)
return utils.RenderError(c, 429) return c.Render("errors/429", fiber.Map{
"path": c.Path(),
})
} }
if err != nil { if err != nil {
return err return err
@ -60,19 +64,23 @@ func HandleUserComments(c *fiber.Ctx) error {
user, err := ApiClient.FetchUser(c.Params("userID")) user, err := ApiClient.FetchUser(c.Params("userID"))
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429) return c.Status(429).Render("errors/429", fiber.Map{
"path": c.Path(),
})
} }
if err != nil { if err != nil {
return err return err
} }
if user.Username == "" { if user.Username == "" {
return utils.RenderError(c, 404) return c.Status(404).Render("errors/404", nil)
} }
comments, err := ApiClient.FetchUserComments(c.Params("userID")) comments, err := ApiClient.FetchUserComments(c.Params("userID"))
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429) c.Status(429)
return utils.RenderError(c, 429) return c.Render("errors/429", fiber.Map{
"path": c.Path(),
})
} }
if err != nil { if err != nil {
return err return err
@ -83,47 +91,3 @@ func HandleUserComments(c *fiber.Ctx) error {
"comments": comments, "comments": comments,
}) })
} }
func HandleUserFavorites(c *fiber.Ctx) error {
utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=604800")
c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
page := "0"
if c.Query("page") != "" {
page = c.Query("page")
}
pageNumber, err := strconv.Atoi(c.Query("page"))
if err != nil {
pageNumber = 0
}
user, err := ApiClient.FetchUser(c.Params("userID"))
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
}
if err != nil {
return err
}
if user.Username == "" {
return utils.RenderError(c, 404)
}
favorites, err := ApiClient.FetchUserFavorites(c.Params("userID"), "newest", page)
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
}
if err != nil {
return err
}
return c.Render("userFavorites", fiber.Map{
"user": user,
"favorites": favorites,
"page": page,
"nextPage": pageNumber + 1,
"prevPage": pageNumber - 1,
})
}

View File

@ -1,547 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@tailwindcss/cli':
specifier: ^4.1.5
version: 4.1.5
tailwindcss:
specifier: ^4.1.5
version: 4.1.5
packages:
'@parcel/watcher-android-arm64@2.5.1':
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [android]
'@parcel/watcher-darwin-arm64@2.5.1':
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.5.1':
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.1':
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [freebsd]
'@parcel/watcher-linux-arm-glibc@2.5.1':
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [win32]
'@parcel/watcher-win32-ia32@2.5.1':
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
engines: {node: '>= 10.0.0'}
cpu: [ia32]
os: [win32]
'@parcel/watcher-win32-x64@2.5.1':
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [win32]
'@parcel/watcher@2.5.1':
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@tailwindcss/cli@4.1.5':
resolution: {integrity: sha512-Kr567rDwDjY1VUnfqh5/+DCpRf4B8lPs5O9flP4kri7n4AM2aubrIxGSh5GN8s+awUKw/U4+6kNlEnZbBNfUeg==}
hasBin: true
'@tailwindcss/node@4.1.5':
resolution: {integrity: sha512-CBhSWo0vLnWhXIvpD0qsPephiaUYfHUX3U9anwDaHZAeuGpTiB3XmsxPAN6qX7bFhipyGBqOa1QYQVVhkOUGxg==}
'@tailwindcss/oxide-android-arm64@4.1.5':
resolution: {integrity: sha512-LVvM0GirXHED02j7hSECm8l9GGJ1RfgpWCW+DRn5TvSaxVsv28gRtoL4aWKGnXqwvI3zu1GABeDNDVZeDPOQrw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.1.5':
resolution: {integrity: sha512-//TfCA3pNrgnw4rRJOqavW7XUk8gsg9ddi8cwcsWXp99tzdBAZW0WXrD8wDyNbqjW316Pk2hiN/NJx/KWHl8oA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.1.5':
resolution: {integrity: sha512-XQorp3Q6/WzRd9OalgHgaqgEbjP3qjHrlSUb5k1EuS1Z9NE9+BbzSORraO+ecW432cbCN7RVGGL/lSnHxcd+7Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.1.5':
resolution: {integrity: sha512-bPrLWbxo8gAo97ZmrCbOdtlz/Dkuy8NK97aFbVpkJ2nJ2Jo/rsCbu0TlGx8joCuA3q6vMWTSn01JY46iwG+clg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.5':
resolution: {integrity: sha512-1gtQJY9JzMAhgAfvd/ZaVOjh/Ju/nCoAsvOVJenWZfs05wb8zq+GOTnZALWGqKIYEtyNpCzvMk+ocGpxwdvaVg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.1.5':
resolution: {integrity: sha512-dtlaHU2v7MtdxBXoqhxwsWjav7oim7Whc6S9wq/i/uUMTWAzq/gijq1InSgn2yTnh43kR+SFvcSyEF0GCNu1PQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-arm64-musl@4.1.5':
resolution: {integrity: sha512-fg0F6nAeYcJ3CriqDT1iVrqALMwD37+sLzXs8Rjy8Z1ZHshJoYceodfyUwGJEsQoTyWbliFNRs2wMQNXtT7MVA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-x64-gnu@4.1.5':
resolution: {integrity: sha512-SO+F2YEIAHa1AITwc8oPwMOWhgorPzzcbhWEb+4oLi953h45FklDmM8dPSZ7hNHpIk9p/SCZKUYn35t5fjGtHA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-linux-x64-musl@4.1.5':
resolution: {integrity: sha512-6UbBBplywkk/R+PqqioskUeXfKcBht3KU7juTi1UszJLx0KPXUo10v2Ok04iBJIaDPkIFkUOVboXms5Yxvaz+g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-wasm32-wasi@4.1.5':
resolution: {integrity: sha512-hwALf2K9FHuiXTPqmo1KeOb83fTRNbe9r/Ixv9ZNQ/R24yw8Ge1HOWDDgTdtzntIaIUJG5dfXCf4g9AD4RiyhQ==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.1.5':
resolution: {integrity: sha512-oDKncffWzaovJbkuR7/OTNFRJQVdiw/n8HnzaCItrNQUeQgjy7oUiYpsm9HUBgpmvmDpSSbGaCa2Evzvk3eFmA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.1.5':
resolution: {integrity: sha512-WiR4dtyrFdbb+ov0LK+7XsFOsG+0xs0PKZKkt41KDn9jYpO7baE3bXiudPVkTqUEwNfiglCygQHl2jklvSBi7Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.1.5':
resolution: {integrity: sha512-1n4br1znquEvyW/QuqMKQZlBen+jxAbvyduU87RS8R3tUSvByAkcaMTkJepNIrTlYhD+U25K4iiCIxE6BGdRYA==}
engines: {node: '>= 10'}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
hasBin: true
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
lightningcss-darwin-arm64@1.29.2:
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.29.2:
resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.29.2:
resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.29.2:
resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.29.2:
resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-arm64-musl@1.29.2:
resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-x64-gnu@1.29.2:
resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-linux-x64-musl@1.29.2:
resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-win32-arm64-msvc@1.29.2:
resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.29.2:
resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.29.2:
resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
engines: {node: '>= 12.0.0'}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
tailwindcss@4.1.5:
resolution: {integrity: sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==}
tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
snapshots:
'@parcel/watcher-android-arm64@2.5.1':
optional: true
'@parcel/watcher-darwin-arm64@2.5.1':
optional: true
'@parcel/watcher-darwin-x64@2.5.1':
optional: true
'@parcel/watcher-freebsd-x64@2.5.1':
optional: true
'@parcel/watcher-linux-arm-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-arm-musl@2.5.1':
optional: true
'@parcel/watcher-linux-arm64-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-arm64-musl@2.5.1':
optional: true
'@parcel/watcher-linux-x64-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-x64-musl@2.5.1':
optional: true
'@parcel/watcher-win32-arm64@2.5.1':
optional: true
'@parcel/watcher-win32-ia32@2.5.1':
optional: true
'@parcel/watcher-win32-x64@2.5.1':
optional: true
'@parcel/watcher@2.5.1':
dependencies:
detect-libc: 1.0.3
is-glob: 4.0.3
micromatch: 4.0.8
node-addon-api: 7.1.1
optionalDependencies:
'@parcel/watcher-android-arm64': 2.5.1
'@parcel/watcher-darwin-arm64': 2.5.1
'@parcel/watcher-darwin-x64': 2.5.1
'@parcel/watcher-freebsd-x64': 2.5.1
'@parcel/watcher-linux-arm-glibc': 2.5.1
'@parcel/watcher-linux-arm-musl': 2.5.1
'@parcel/watcher-linux-arm64-glibc': 2.5.1
'@parcel/watcher-linux-arm64-musl': 2.5.1
'@parcel/watcher-linux-x64-glibc': 2.5.1
'@parcel/watcher-linux-x64-musl': 2.5.1
'@parcel/watcher-win32-arm64': 2.5.1
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
'@tailwindcss/cli@4.1.5':
dependencies:
'@parcel/watcher': 2.5.1
'@tailwindcss/node': 4.1.5
'@tailwindcss/oxide': 4.1.5
enhanced-resolve: 5.18.1
mri: 1.2.0
picocolors: 1.1.1
tailwindcss: 4.1.5
'@tailwindcss/node@4.1.5':
dependencies:
enhanced-resolve: 5.18.1
jiti: 2.4.2
lightningcss: 1.29.2
tailwindcss: 4.1.5
'@tailwindcss/oxide-android-arm64@4.1.5':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.1.5':
optional: true
'@tailwindcss/oxide-darwin-x64@4.1.5':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.1.5':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.1.5':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.1.5':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.1.5':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.1.5':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.1.5':
optional: true
'@tailwindcss/oxide@4.1.5':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.1.5
'@tailwindcss/oxide-darwin-arm64': 4.1.5
'@tailwindcss/oxide-darwin-x64': 4.1.5
'@tailwindcss/oxide-freebsd-x64': 4.1.5
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.5
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.5
'@tailwindcss/oxide-linux-arm64-musl': 4.1.5
'@tailwindcss/oxide-linux-x64-gnu': 4.1.5
'@tailwindcss/oxide-linux-x64-musl': 4.1.5
'@tailwindcss/oxide-wasm32-wasi': 4.1.5
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.5
'@tailwindcss/oxide-win32-x64-msvc': 4.1.5
braces@3.0.3:
dependencies:
fill-range: 7.1.1
detect-libc@1.0.3: {}
detect-libc@2.0.4: {}
enhanced-resolve@5.18.1:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.1
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
graceful-fs@4.2.11: {}
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
jiti@2.4.2: {}
lightningcss-darwin-arm64@1.29.2:
optional: true
lightningcss-darwin-x64@1.29.2:
optional: true
lightningcss-freebsd-x64@1.29.2:
optional: true
lightningcss-linux-arm-gnueabihf@1.29.2:
optional: true
lightningcss-linux-arm64-gnu@1.29.2:
optional: true
lightningcss-linux-arm64-musl@1.29.2:
optional: true
lightningcss-linux-x64-gnu@1.29.2:
optional: true
lightningcss-linux-x64-musl@1.29.2:
optional: true
lightningcss-win32-arm64-msvc@1.29.2:
optional: true
lightningcss-win32-x64-msvc@1.29.2:
optional: true
lightningcss@1.29.2:
dependencies:
detect-libc: 2.0.4
optionalDependencies:
lightningcss-darwin-arm64: 1.29.2
lightningcss-darwin-x64: 1.29.2
lightningcss-freebsd-x64: 1.29.2
lightningcss-linux-arm-gnueabihf: 1.29.2
lightningcss-linux-arm64-gnu: 1.29.2
lightningcss-linux-arm64-musl: 1.29.2
lightningcss-linux-x64-gnu: 1.29.2
lightningcss-linux-x64-musl: 1.29.2
lightningcss-win32-arm64-msvc: 1.29.2
lightningcss-win32-x64-msvc: 1.29.2
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
mri@1.2.0: {}
node-addon-api@7.1.1: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
tailwindcss@4.1.5: {}
tapable@2.2.1: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0

View File

@ -1,7 +1,6 @@
{ {
"name": "rimgo", "name": "rimgo",
"short_name": "rimgo", "short_name": "rimgo",
"start_url": "/",
"icons": [ "icons": [
{ {
"src": "/static/favicon/android-chrome-192x192.png", "src": "/static/favicon/android-chrome-192x192.png",

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M106.91 149.09A71.53 71.53 0 0 1 128 200a8 8 0 0 1-16 0a56 56 0 0 0-56-56a8 8 0 0 1 0-16a71.53 71.53 0 0 1 50.91 21.09M56 80a8 8 0 0 0 0 16a104 104 0 0 1 104 104a8 8 0 0 0 16 0A120 120 0 0 0 56 80m118.79 1.21A166.9 166.9 0 0 0 56 32a8 8 0 0 0 0 16a151 151 0 0 1 107.48 44.52A151 151 0 0 1 208 200a8 8 0 0 0 16 0a166.9 166.9 0 0 0-49.21-118.79M60 184a12 12 0 1 0 12 12a12 12 0 0 0-12-12"/></svg>

Before

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,11 +1,13 @@
@import "tailwindcss" source("../views"); @tailwind base;
@tailwind components;
@tailwind utilities;
body { body {
margin: 0 24vw; margin: 0 24vw;
} }
p a { p a {
@apply break-words underline text-decoration: underline
} }
.posts { .posts {

8
tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
content: ["./views/*.hbs", "./views/**/*.hbs"],
theme: {
extend: {},
},
variants: {},
plugins: [],
};

View File

@ -9,7 +9,6 @@ type config struct {
Port string Port string
Addr string Addr string
ImgurId string ImgurId string
Secure bool
FiberPrefork bool FiberPrefork bool
ImageCache bool ImageCache bool
CleanupInterval time.Duration CleanupInterval time.Duration
@ -48,7 +47,6 @@ func LoadConfig() {
Port: port, Port: port,
Addr: addr, Addr: addr,
ImgurId: imgurId, ImgurId: imgurId,
Secure: os.Getenv("SECURE") == "true",
FiberPrefork: os.Getenv("FIBER_PREFORK") == "true", FiberPrefork: os.Getenv("FIBER_PREFORK") == "true",
Privacy: map[string]interface{}{ Privacy: map[string]interface{}{
"set": os.Getenv("PRIVACY_NOT_COLLECTED") != "", "set": os.Getenv("PRIVACY_NOT_COLLECTED") != "",

View File

@ -1,25 +0,0 @@
package utils
import (
"strconv"
"strings"
"codeberg.org/rimgo/rimgo/static"
"github.com/gofiber/fiber/v2"
)
func RenderError(c *fiber.Ctx, code int) error {
if !strings.Contains(c.Get("Accept"), "html") && c.Params("extension") != "" {
codeStr := "generic"
if code != 0 {
codeStr = strconv.Itoa(code)
}
img, _ := static.GetFiles().ReadFile("img/error-" + codeStr + ".png")
c.Set("Content-Type", "image/png")
return c.Status(code).Send(img)
} else {
return c.Status(code).Render("errors/" + strconv.Itoa(code), fiber.Map{
"path": c.Path(),
})
}
}

View File

@ -1,11 +0,0 @@
package utils
import "github.com/gofiber/fiber/v2"
func GetInstanceUrl(c *fiber.Ctx) string {
proto := "https://"
if !Config.Secure {
proto = "http://"
}
return proto + c.Hostname()
}

View File

@ -25,8 +25,8 @@
rimgo is not affiliated with Imgur, all content is proxied from Imgur. rimgo is not affiliated with Imgur, all content is proxied from Imgur.
</p> </p>
<br/> <br/>
<h3 class="font-bold text-xl">Legal notice</h3> <h3 class="font-bold text-xl">Notice</h3>
<p>rimgo does not allow uploads or host any content, media. All content on any rimgo instances is from Imgur™. Imgur is a trademark of Imgur, Inc. Any issues with content on rimgo should be be reported to Imgur. rimgo is not affiliated with Imgur, Inc.</p> <p>All images and media are from Imgur. rimgo does not allow uploads or comments. Any issues with content should be reported to Imgur.</p>
</main> </main>
<h2 class="font-bold text-2xl">This instance</h2> <h2 class="font-bold text-2xl">This instance</h2>

View File

@ -3,8 +3,8 @@
<img class="object-cover block w-full h-[300px] sm:w-[120px] sm:h-[140px] rounded-lg rounded-b-none sm:rounded-b-lg" src="{{this.Post.Cover.Url}}" alt=""> <img class="object-cover block w-full h-[300px] sm:w-[120px] sm:h-[140px] rounded-lg rounded-b-none sm:rounded-b-lg" src="{{this.Post.Cover.Url}}" alt="">
<div class="flex flex-col gap-2 bg-slate-600 p-4 rounded-lg rounded-t-none sm:rounded-t-lg w-full"> <div class="flex flex-col gap-2 bg-slate-600 p-4 rounded-lg rounded-t-none sm:rounded-t-lg w-full">
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<p class="md-container">{{{this.Comment}}}</p> <p>{{{this.Comment}}}</p>
<div class="grow"></div> <div class="flex-grow"></div>
<div class="flex gap-2"> <div class="flex gap-2">
<span title="{{this.CreatedAt}}">{{this.RelTime}}</span> <span title="{{this.CreatedAt}}">{{this.RelTime}}</span>
{{#if this.DeletedAt}} {{#if this.DeletedAt}}

View File

@ -25,8 +25,7 @@
</header> </header>
<main> <main>
<div class="flex flex-col sm:flex-row my-4 w-full justify-between"> <div class="flex gap-4 items-center my-4">
<div class="flex flex-col gap-2 md:flex-row md:gap-4 md:items-center">
{{#if post.User.Username}} {{#if post.User.Username}}
<a href="/user/{{post.User.Username}}" class="flex gap-2 items-center"> <a href="/user/{{post.User.Username}}" class="flex gap-2 items-center">
<img src="{{post.User.Avatar}}" class="rounded-full" width="36" height="36" /> <img src="{{post.User.Avatar}}" class="rounded-full" width="36" height="36" />
@ -52,14 +51,8 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{#noteq next ""}}
<a href="{{next}}" class="self-end">
<button class="p-2 rounded-lg bg-slate-600">Next &gt;</button>
</a>
{{/noteq}}
</div>
<div class="flex flex-center flex-col break-words"> <div class="flex flex-center flex-col">
{{#each post.Media}} {{#each post.Media}}
{{#if this.Title}} {{#if this.Title}}
<h4 class="font-bold">{{this.Title}}</h4> <h4 class="font-bold">{{this.Title}}</h4>
@ -108,8 +101,7 @@
{{#if comments}} {{#if comments}}
<div> <div>
<input id="comments__expandBtn" type="checkbox" checked> <input id="comments__expandBtn" type="checkbox" checked>
<label class="comments__expandBtn__label my-2 py-4 border-solid border-t-2 border-slate-400" <label class="comments__expandBtn__label my-2 py-4 border-solid border-t-2 border-slate-400" for="comments__expandBtn">
for="comments__expandBtn">
<h3 class="text-xl font-bold">Comments ({{post.Comments}})</h3> <h3 class="text-xl font-bold">Comments ({{post.Comments}})</h3>
<span class="text-xl font-bold"></span> <span class="text-xl font-bold"></span>
</label> </label>

View File

@ -22,10 +22,10 @@
</div> </div>
<div class="flex justify-between mt-4 font-bold"> <div class="flex justify-between mt-4 font-bold">
{{#noteq page "0"}} {{#if displayPrev}}
<a href="/search?q={{query}}&page={{prevPage}}">Previous page</a> <a href="/search?q={{query}}&page={{prevPage}}">Previous page</a>
{{/noteq}} {{/if}}
<p>Page {{nextPage}}</p> <p>Page {{page}}</p>
<a href="/search?q={{query}}&page={{nextPage}}">Next page</a> <a href="/search?q={{query}}&page={{nextPage}}">Next page</a>
</div> </div>
</main> </main>

View File

@ -5,9 +5,6 @@
<title>{{tag.Display}} - rimgo</title> <title>{{tag.Display}} - rimgo</title>
{{> partials/head }} {{> partials/head }}
<link href="/t/{{tag.Tag}}.rss" rel="alternate" title={{tag.Display}} type="application/rss+xml"
<link href="/t/{{tag.Tag}}.atom" rel="alternate" title={{tag.Display}} type="application/atom+xml">
<link href="/t/{{tag.Tag}}.json" rel="alternate" title={{tag.Display}} type="application/feed+json">
</head> </head>
<body class="font-sans text-lg bg-slate-800 text-white"> <body class="font-sans text-lg bg-slate-800 text-white">
@ -19,12 +16,7 @@
<header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{tag.Background}}');"> <header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{tag.Background}}');">
<div class="flex flex-col items-center justify-center text-center"> <div class="flex flex-col items-center justify-center text-center">
<div class="flex gap-2 items-center">
<h2 class="text-2xl font-bold">{{tag.Display}}</h2> <h2 class="text-2xl font-bold">{{tag.Display}}</h2>
<a href="/t/{{tag.Tag}}.rss" label="RSS">
<img src="/static/icons/PhRss.svg" width="24px" height="24px" class="invert" />
</a>
</div>
<p>{{tag.PostCount}} posts</p> <p>{{tag.PostCount}} posts</p>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
@ -53,11 +45,10 @@
{{/each}} {{/each}}
</div> </div>
<div class="flex justify-between mt-4 font-bold"> <div class="mt-4 font-bold">
{{#noteq page "1"}} {{#if displayPrev}}
<a href="{{channel.RelUrl}}?page={{prevPage}}">Previous page</a> <a href="{{channel.RelUrl}}?page={{prevPage}}">Previous page</a>
{{/noteq}} {{/if}}
<p>Page {{nextPage}}</p>
<a href="{{channel.RelUrl}}?page={{nextPage}}">Next page</a> <a href="{{channel.RelUrl}}?page={{nextPage}}">Next page</a>
</div> </div>
</main> </main>

View File

@ -5,9 +5,6 @@
<title>Trending - rimgo</title> <title>Trending - rimgo</title>
{{> partials/head }} {{> partials/head }}
<link href="/trending.rss" rel="alternate" title="Trending" type="application/rss+xml">
<link href="/trending.atom" rel="alternate" title="Trending" type="application/atom+xml">
<link href="/trending.json" rel="alternate" title="Trending" type="application/feed+json">
</head> </head>
<body class="font-sans text-lg bg-slate-800 text-white"> <body class="font-sans text-lg bg-slate-800 text-white">
@ -18,11 +15,8 @@
</section> </section>
<header class="p-4 rounded-xl text-white mb-4 bg-gradient-to-r from-blue-400 to-emerald-400"> <header class="p-4 rounded-xl text-white mb-4 bg-gradient-to-r from-blue-400 to-emerald-400">
<div class="flex items-center justify-center text-center gap-2"> <div class="flex flex-col items-center justify-center text-center">
<h2 class="text-2xl font-extrabold">Trending</h2> <h2 class="text-2xl font-extrabold">Trending</h2>
<a href="/trending.rss" label="RSS">
<img src="/static/icons/PhRss.svg" width="24px" height="24px" class="invert" />
</a>
</div> </div>
<div class="flex flex-col sm:flex-row sm:justify-between"> <div class="flex flex-col sm:flex-row sm:justify-between">
<div class="flex flex-col"> <div class="flex flex-col">
@ -71,9 +65,9 @@
</div> </div>
<div class="flex justify-between mt-4 font-bold"> <div class="flex justify-between mt-4 font-bold">
{{#noteq page "1"}} {{#if displayPrev}}
<a href="/trending?section={{section}}&sort={{sort}}&page={{prevPage}}">Previous page</a> <a href="/trending?section={{section}}&sort={{sort}}&page={{prevPage}}">Previous page</a>
{{/noteq}} {{/if}}
<p>Page {{page}}</p> <p>Page {{page}}</p>
<a href="/trending?section={{section}}&sort={{sort}}&page={{nextPage}}">Next page</a> <a href="/trending?section={{section}}&sort={{sort}}&page={{nextPage}}">Next page</a>
</div> </div>

View File

@ -5,9 +5,6 @@
<title>{{user.Username}} - rimgo</title> <title>{{user.Username}} - rimgo</title>
{{> partials/head }} {{> partials/head }}
<link href="/user/{{user.Username}}.rss" rel="alternate" title={{user.Username}} type="application/rss+xml">
<link href="/user/{{user.Username}}.atom" rel="alternate" title={{user.Username}} type="application/atom+xml">
<link href="/user/{{user.Username}}.json" rel="alternate" title={{user.Username}} type="application/feed+json">
</head> </head>
<body class="font-sans text-lg bg-slate-800 text-white"> <body class="font-sans text-lg bg-slate-800 text-white">
@ -18,21 +15,15 @@
</section> </section>
<header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{user.Cover}}');"> <header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{user.Cover}}');">
<div class="flex flex-col sm:flex-row items-center gap-2"> <div class="flex items-center gap-2">
<img class="rounded-full" src="{{user.Avatar}}" width="72" height="72"> <img class="rounded-full" src="{{user.Avatar}}" width="72" height="72">
<div class="items-center sm:items-start text-center sm:text-left"> <div>
<div class="flex flex-row gap-2 items-center">
<h2 class="font-bold text-2xl">{{user.Username}}</h2> <h2 class="font-bold text-2xl">{{user.Username}}</h2>
<a href="/user/{{user.Username}}.rss" label="RSS">
<img src="/static/icons/PhRss.svg" width="24px" height="24px" class="invert" />
</a>
</div>
<p>{{user.Points}} pts · {{user.CreatedAt}}</p> <p>{{user.Points}} pts · {{user.CreatedAt}}</p>
</div> </div>
<hr class="sm:border-0 grow"> <hr class="sm:border-0 flex-grow">
<div class="flex flex-col items-center sm:items-end"> <div class="flex flex-col sm:items-end">
<a href="/user/{{user.Username}}"><b>Submissions</b></a> <a href="/user/{{user.Username}}"><b>Submissions</b></a>
<a href="/user/{{user.Username}}/favorites">Favorites</a>
<a href="/user/{{user.Username}}/comments">Comments</a> <a href="/user/{{user.Username}}/comments">Comments</a>
</div> </div>
</div> </div>

View File

@ -21,10 +21,9 @@
<h2 class="font-bold text-2xl">{{user.Username}}</h2> <h2 class="font-bold text-2xl">{{user.Username}}</h2>
<p>{{user.Points}} pts · {{user.CreatedAt}}</p> <p>{{user.Points}} pts · {{user.CreatedAt}}</p>
</div> </div>
<hr class="sm:border-0 grow"> <hr class="sm:border-0 flex-grow">
<div class="flex flex-col items-center sm:items-end"> <div class="flex flex-col items-center sm:items-end">
<a href="/user/{{user.Username}}">Submissions</a> <a href="/user/{{user.Username}}">Submissions</a>
<a href="/user/{{user.Username}}/favorites">Favorites</a>
<a href="/user/{{user.Username}}/comments"><b>Comments</b></a> <a href="/user/{{user.Username}}/comments"><b>Comments</b></a>
</div> </div>
</div> </div>

View File

@ -1,52 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{user.Username}}'s favorites - rimgo</title>
{{> partials/head }}
</head>
<body class="font-sans text-lg bg-slate-800 text-white">
{{> partials/nav }}
<section class="my-4 w-full flex flex-col items-center">
{{> partials/searchBar }}
</section>
<header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{user.Cover}}');">
<div class="flex flex-col sm:flex-row items-center gap-2">
<img class="rounded-full" src="{{user.Avatar}}" width="72" height="72">
<div class="items-center sm:items-start text-center sm:text-left">
<h2 class="font-bold text-2xl">{{user.Username}}</h2>
<p>{{user.Points}} pts · {{user.CreatedAt}}</p>
</div>
<hr class="sm:border-0 grow">
<div class="flex flex-col items-center sm:items-end">
<a href="/user/{{user.Username}}">Submissions</a>
<a href="/user/{{user.Username}}/favorites"><b>Favorites</b></a>
<a href="/user/{{user.Username}}/comments">Comments</a>
</div>
</div>
<p class="mt-2">{{user.Bio}}</p>
</header>
<main>
<div class="posts">
{{#each favorites}}
{{> partials/post }}
{{/each}}
</div>
<div class="flex mt-4 font-bold justify-between">
{{#noteq page "0" }}
<a href="{{channel.RelUrl}}?page={{prevPage}}">Previous page</a>
{{/noteq}}
<a href="{{channel.RelUrl}}?page={{nextPage}}">Next page</a>
</div>
</main>
{{> partials/footer }}
</body>
</html>