24 Commits

Author SHA1 Message Date
orangix
b78ed44730 allow external origin to access media files 2026-01-25 07:23:08 +01:00
orangix
c208a55f40 go mod tidy 2026-01-23 20:27:20 +01:00
orangix
4441d25d38 fix errors 2026-01-23 20:18:56 +01:00
orangix
975ffa0b9c uncomment stack handler 2026-01-23 19:42:55 +01:00
orangix
7b1314fae3 rss 2026-01-23 19:27:24 +01:00
orangix
fd704f53e7 update getUrl.go to use net/http 2026-01-19 19:25:46 +01:00
orangix
bf849e1cbc remove FIBER_PREFORK 2026-01-19 19:17:36 +01:00
orangix
cd4a36c9f7 port most routes 2026-01-19 19:11:01 +01:00
orangix
04fbc7f5f4 fix embed handling 2026-01-19 17:26:45 +01:00
orangix
189ebeefde add noteq 2026-01-19 17:08:15 +01:00
orangix
3f40c25b04 port template engine 2026-01-19 05:43:04 +01:00
orangix
33fa04e98d change pages/about.go to Config.ForceWebp 2026-01-19 03:04:06 +01:00
orangix
e5b87dc924 gofmt 2026-01-19 02:36:52 +01:00
orangix
8cb2524924 drop RIMGU_ environment variables 2026-01-16 22:55:45 +01:00
video-prize-ranch
23b66cba47 Use full instance url for cache key (#243)
Closes #240

Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/243
Reviewed-by: orangix <orangix@noreply.codeberg.org>
Co-authored-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
Co-committed-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
2026-01-14 18:27:25 +01:00
video-prize-ranch
eabb7d9917 Allow using 1 or true for all boolean config options (#242)
Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/242
Reviewed-by: orangix <orangix@noreply.codeberg.org>
Co-authored-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
Co-committed-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
2026-01-12 01:52:26 +01:00
video-prize-ranch
3b8ad3f360 Force WebP improvements (#237)
- Allow using `true` or `1` in configuration
- Set Content-Disposition header to use correct file extension when using "Save image as"
- Only rewrite requests from img elements using Sec-Fetch-Dest header

#200
#201

Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/237
Reviewed-by: orangix <orangix@noreply.codeberg.org>
Co-authored-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
Co-committed-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
2026-01-12 01:46:53 +01:00
orangix
e8ed026962 add protocol to cache key (#241)
fixes #240

Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/241
Reviewed-by: video-prize-ranch <video-prize-ranch@noreply.codeberg.org>
Co-authored-by: orangix <uleo8b8g@anonaddy.me>
Co-committed-by: orangix <uleo8b8g@anonaddy.me>
2026-01-12 01:45:24 +01:00
orangix
107a45e58b Merge pull request 'implement x-forwarded-proto' (#239) from x-forwarded-for into main
Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/239
Reviewed-by: video-prize-ranch <video-prize-ranch@noreply.codeberg.org>
2026-01-06 04:55:44 +01:00
video-prize-ranch
751616d967 Update Go modules (#238)
Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/238
Reviewed-by: orangix <orangix@noreply.codeberg.org>
Co-authored-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
Co-committed-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
2026-01-06 01:31:06 +01:00
orangix
09222a116f implement x-forwarded-proto 2026-01-05 08:18:55 +01:00
orangix
42174c052d Merge pull request 'Update README' (#231) from A_Cat/rimgo:pr-readme into main
Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/231
Reviewed-by: orangix <orangix@noreply.codeberg.org>
2025-10-29 03:54:02 +01:00
A_Cat
c22365100d Update README 2025-10-25 07:01:16 +02:00
video-prize-ranch
26edb6a385 Fix docker files and update tailwind (#216)
- Update tailwind to 4.1.5
- Remove `version` in docker-compose.yml
- Fix CSS build in dockerfile

closes #202

Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/216
Co-authored-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
Co-committed-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
2025-05-02 18:33:26 +00:00
36 changed files with 736 additions and 592 deletions

View File

@@ -3,14 +3,14 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETARCH
WORKDIR /src
RUN apk --no-cache add ca-certificates git nodejs npm
RUN apk --no-cache add ca-certificates git nodejs pnpm
COPY . .
RUN npx @tailwindcss/cli -i static/tailwind.css -o static/app.css -m
RUN pnpm install && pnpm run build
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)"
FROM scratch as bin
FROM scratch AS bin
WORKDIR /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

View File

@@ -17,8 +17,6 @@ An alternative frontend for Imgur. Originally based on [rimgu](https://codeberg.
- [Privacy](#privacy)
- [Usage](#usage)
- [Instances](#instances)
- [Clearnet](#clearnet)
- [Tor](#tor)
- [Contributing](#contributing)
- [License](#license)
@@ -28,7 +26,7 @@ Our new documentation is now available at [https://rimgo.codeberg.page/docs/](ht
- [Install](https://rimgo.codeberg.page/docs/getting-started/install/)
- [Configuration](https://rimgo.codeberg.page/docs/usage/configuration/)
- [Redirection](https://rimgo.codeberg.page/docs/usage/configuration/)
- [Redirection](https://rimgo.codeberg.page/docs/usage/redirection/)
- [Instance privacy](https://rimgo.codeberg.page/docs/usage/instance-privacy/)
## Features
@@ -52,12 +50,12 @@ Tested using [Google PageSpeed Insights](https://pagespeed.web.dev/).
| Time to Interactive | 1.6s | 23.8s |
### Privacy
Imgur collects information about your device and uses tracking cookies for advertising, this is mentioned in their [privacy policy](https://imgur.com/privacy/). [Blacklight](https://themarkup.org/blacklight) found 31 trackers and 87 third-party cookies.
Imgur collects information about your device and uses tracking cookies for advertising, this is mentioned in their [privacy policy](https://imgur.com/privacy/). [Blacklight](https://themarkup.org/blacklight) found 84 trackers and 264 third-party cookies.
See what cookies and trackers Imgur uses and where your data gets sent: https://themarkup.org/blacklight?url=imgur.com
## Usage
Replace imgur.com or i.imgur.com with the instance domain. For i.stack.imgur.com, replace i.stack.imgur.com with the instance domain and add stack/ before the media ID. You can use a browser extension to do this [automatically](#automatically-redirect-links).
Replace imgur.com or i.imgur.com with the instance domain. For i.stack.imgur.com, replace i.stack.imgur.com with the instance domain and add stack/ before the media ID.
Imgur: `https://imgur.com/gallery/j2sOQkJ` -> `https://rimgo.bcow.xyz/gallery/j2sOQkJ`
Stack Overflow: `https://i.stack.imgur.com/KnO3v.jpg?s=64&g=1` -> `https://rimgo.bcow.xyz/stack/KnO3v.jpg?s=64&g=1`

View File

@@ -7,15 +7,15 @@ import (
)
type Client struct {
ClientID string
Cache *cache.Cache
ClientID string
Cache *cache.Cache
}
func NewClient(clientId string) (*Client) {
client := Client{
ClientID: clientId,
Cache: cache.New(15*time.Minute, 15*time.Minute),
}
func NewClient(clientId string) *Client {
client := Client{
ClientID: clientId,
Cache: cache.New(15*time.Minute, 15*time.Minute),
}
return &client
return &client
}

View File

@@ -11,19 +11,19 @@ import (
)
type SearchResult struct {
Id string
Url string
ImageUrl string
Title string
User string
Points string
Views string
RelTime string
Id string
Url string
ImageUrl string
Title string
User string
Points string
Views string
RelTime string
}
func (client *Client) Search(query string, page string) ([]SearchResult, error) {
query = url.QueryEscape(query)
req, err := http.NewRequest("GET", "https://imgur.com/search/all/page/" + page + "?scrolled&q_size_is_mpx=off&qs=list&q=" + query, nil)
req, err := http.NewRequest("GET", "https://imgur.com/search/all/page/"+page+"?scrolled&q_size_is_mpx=off&qs=list&q="+query, nil)
if err != nil {
return []SearchResult{}, err
}
@@ -35,16 +35,16 @@ func (client *Client) Search(query string, page string) ([]SearchResult, error)
}
defer res.Body.Close()
if res.StatusCode != 200 {
return []SearchResult{}, fmt.Errorf("invalid status code, got %d", res.StatusCode)
}
return []SearchResult{}, fmt.Errorf("invalid status code, got %d", res.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
return []SearchResult{}, err
}
if err != nil {
return []SearchResult{}, err
}
results := []SearchResult{}
doc.Find(".post-list").Each(func(i int, s *goquery.Selection) {
doc.Find(".post-list").Each(func(i int, s *goquery.Selection) {
url, _ := s.Find("a").Attr("href")
imageUrl, _ := s.Find("img").Attr("src")
@@ -55,14 +55,14 @@ func (client *Client) Search(query string, page string) ([]SearchResult, error)
views = strings.TrimSuffix(views, " views")
result := SearchResult{
Id: strings.Split(url, "/")[2],
Url: url,
Id: strings.Split(url, "/")[2],
Url: url,
ImageUrl: strings.ReplaceAll(imageUrl, "//i.imgur.com", ""),
Title: s.Find(".search-item-title a").Text(),
User: s.Find(".account").Text(),
Views: views,
Points: points,
RelTime: strings.TrimSpace(postInfo[2]),
Title: s.Find(".search-item-title a").Text(),
User: s.Find(".account").Text(),
Views: views,
Points: points,
RelTime: strings.TrimSpace(postInfo[2]),
}
results = append(results, result)

View File

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

31
go.mod
View File

@@ -1,14 +1,10 @@
module codeberg.org/rimgo/rimgo
go 1.23
toolchain go1.23.5
go 1.24.0
require (
github.com/PuerkitoBio/goquery v1.10.1
github.com/PuerkitoBio/goquery v1.11.0
github.com/dustin/go-humanize v1.0.1
github.com/gofiber/fiber/v2 v2.52.6
github.com/gofiber/template/handlebars/v2 v2.1.11
github.com/gorilla/feeds v1.2.0
github.com/joho/godotenv v1.5.1
github.com/mailgun/raymond/v2 v2.0.48
@@ -19,27 +15,14 @@ require (
)
require (
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tinylib/msgp v1.2.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.58.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
)

54
go.sum
View File

@@ -1,7 +1,5 @@
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -11,48 +9,25 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/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/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/handlebars/v2 v2.1.11 h1:pgPF+DKuIvCl3z/Kj1u6VA/8hLBljH2Rg6LwVFBm7aM=
github.com/gofiber/template/handlebars/v2 v2.1.11/go.mod h1:AbKfYOgH+ngxaYXtLzafy4AKLAQ2NrJYbvtWaOX82I4=
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/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
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/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -65,21 +40,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
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/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
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/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
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/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
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=
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=
@@ -103,8 +69,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
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/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -120,14 +86,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -145,8 +110,9 @@ 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

224
main.go
View File

@@ -3,23 +3,37 @@ package main
import (
"flag"
"fmt"
"io"
"net/http"
"os"
"time"
"strings"
"codeberg.org/rimgo/rimgo/pages"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/static"
"codeberg.org/rimgo/rimgo/utils"
"codeberg.org/rimgo/rimgo/views"
"github.com/mailgun/raymond/v2"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cache"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/template/handlebars/v2"
"github.com/joho/godotenv"
)
// a handler that returns error if it can't respond
type handler func(w http.ResponseWriter, r *http.Request) error
func wrapHandler(h handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if v := recover(); v != nil {
utils.RenderError(w, r, 500, fmt.Sprint(v))
}
}()
err := h(w, r)
if err != nil {
fmt.Println(err)
utils.RenderError(w, r, 500, err.Error())
}
})
}
func main() {
envPath := flag.String("c", ".env", "Path to env file")
godotenv.Load(*envPath)
@@ -27,120 +41,110 @@ func main() {
pages.InitializeApiClient()
views := http.FS(views.GetFiles())
if os.Getenv("ENV") == "dev" {
views = http.Dir("./views")
}
engine := handlebars.NewFileSystem(views, ".hbs")
views := views.GetFiles()
static := static.GetFiles()
render.Initialize(views)
engine.AddFunc("noteq", func(a interface{}, b interface{}, options *raymond.Options) interface{} {
if raymond.Str(a) != raymond.Str(b) {
return options.Fn()
}
return ""
})
app := http.NewServeMux()
app := fiber.New(fiber.Config{
Views: engine,
Prefork: utils.Config.FiberPrefork,
UnescapePath: true,
StreamRequestBody: true,
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
if e, ok := err.(*fiber.Error); ok {
code = e.Code
}
return utils.RenderError(ctx, code)
},
})
app.Use(recover.New(recover.Config{
EnableStackTrace: true,
StackTraceHandler: func(c *fiber.Ctx, e interface{}) {
fmt.Println(e)
},
app.Handle("GET /static/", http.StripPrefix("/static/", http.FileServerFS(static)))
app.Handle("GET /robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file, _ := static.Open("robots.txt")
defer file.Close()
io.Copy(w, file)
}))
app.Handle("GET /favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file, _ := static.Open("favicon/favicon.ico")
defer file.Close()
io.Copy(w, file)
}))
if os.Getenv("ENV") == "dev" {
app.Use("/static", filesystem.New(filesystem.Config{
Root: http.Dir("./static"),
app.Handle("GET /errors/429", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
utils.RenderError(w, r, 429)
}))
app.Get("/errors/429", func(c *fiber.Ctx) error {
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 {
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 {
return c.Render("errors/error", fiber.Map{
"err": "Test error",
})
})
app.Get("/errors/error/img", func(c *fiber.Ctx) error {
return c.Redirect("/static/img/error-generic.png")
})
} else {
app.Use("/static", filesystem.New(filesystem.Config{
MaxAge: 2592000,
Root: http.FS(static.GetFiles()),
app.Handle("GET /errors/429/img", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "/static/img/error-429.png")
w.WriteHeader(302)
}))
app.Use(cache.New(cache.Config{
Expiration: 30 * time.Minute,
MaxBytes: 25000000,
KeyGenerator: func(c *fiber.Ctx) string {
return c.OriginalURL()
},
CacheControl: true,
StoreResponseHeaders: true,
app.Handle("GET /errors/404", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
utils.RenderError(w, r, 404)
}))
app.Handle("GET /errors/404/img", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "/static/img/error-404.png")
w.WriteHeader(302)
}))
app.Handle("GET /errors/error", wrapHandler(func(w http.ResponseWriter, r *http.Request) error {
return fmt.Errorf("Test error")
}))
app.Handle("GET /errors/panic", wrapHandler(func(w http.ResponseWriter, r *http.Request) error {
panic("Test error")
}))
}
app.Get("/robots.txt", func(c *fiber.Ctx) error {
file, _ := static.GetFiles().ReadFile("robots.txt")
_, err := c.Write(file)
app.Handle("GET /{$}", wrapHandler(pages.HandleFrontpage))
app.Handle("GET /a/{postID}", wrapHandler(pages.HandlePost))
app.Handle("GET /a/{postID}/embed", wrapHandler(pages.HandleEmbed))
app.Handle("GET /t/{tag}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error {
name, ext := utils.SplitNameExt(r.PathValue("tag"))
if ext != "" {
r.SetPathValue("tag", name[0:len(name)-1])
r.SetPathValue("type", ext)
return pages.HandleTagRSS(w, r)
}
return pages.HandleTag(w, r)
}))
app.Handle("GET /t/{tag}/{postID}", wrapHandler(pages.HandlePost))
app.Handle("GET /r/{sub}/{postID}", wrapHandler(pages.HandlePost))
app.Handle("GET /user/{userID}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error {
name, ext := utils.SplitNameExt(r.PathValue("userID"))
if ext != "" {
r.SetPathValue("userID", name[0:len(name)-1])
r.SetPathValue("type", ext)
return pages.HandleUserRSS(w, r)
}
return pages.HandleUser(w, r)
}))
app.Handle("GET /user/{userID}/favorites", wrapHandler(pages.HandleUserFavorites))
app.Handle("GET /user/{userID}/comments", wrapHandler(pages.HandleUserComments))
app.Handle("GET /user/{userID}/cover", wrapHandler(pages.HandleUserCover))
app.Handle("GET /user/{userID}/avatar", wrapHandler(pages.HandleUserAvatar))
app.Handle("GET /gallery/{postID}", wrapHandler(pages.HandlePost))
app.Handle("GET /gallery/{postID}/embed", wrapHandler(pages.HandleEmbed))
app.Handle("GET /{component}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error {
component := r.PathValue("component")
switch {
case component == "about":
return pages.HandleAbout(w, r)
case component == "privacy":
return pages.HandlePrivacy(w, r)
case component == "search":
return pages.HandleSearch(w, r)
case component == "trending":
return pages.HandleTrending(w, r)
case strings.HasPrefix(component, "trending."):
_, ext := utils.SplitNameExt(component)
r.SetPathValue("type", ext)
return pages.HandleTrendingRSS(w, r)
case strings.HasSuffix(component, ".gifv"):
r.SetPathValue("postID", component)
return pages.HandleGifv(w, r)
case strings.Contains(component, "."):
return pages.HandleMedia(w, r)
default:
r.SetPathValue("postID", component)
return pages.HandlePost(w, r)
}
}))
app.Handle("GET /stack/{component}", wrapHandler(pages.HandleMedia))
// matches anything with no more specific route
app.Handle("GET /", wrapHandler(func(w http.ResponseWriter, r *http.Request) error {
err := render.Render(w, "errors/404", nil)
return err
})
app.Get("/favicon.ico", func(c *fiber.Ctx) error {
file, _ := static.GetFiles().ReadFile("favicon/favicon.ico")
_, err := c.Write(file)
return err
})
}))
app.Get("/", pages.HandleFrontpage)
app.Get("/about", pages.HandleAbout)
app.Get("/privacy", pages.HandlePrivacy)
app.Get("/search", pages.HandleSearch)
app.Get("/trending.:type", pages.HandleTrendingRSS)
app.Get("/trending", pages.HandleTrending)
app.Get("/a/:postID", pages.HandlePost)
app.Get("/a/:postID/embed", pages.HandleEmbed)
app.Get("/t/:tag.:type", pages.HandleTagRSS)
app.Get("/t/:tag", pages.HandleTag)
app.Get("/t/:tag/: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/favorites", pages.HandleUserFavorites)
app.Get("/user/:userID/comments", pages.HandleUserComments)
app.Get("/user/:userID/cover", pages.HandleUserCover)
app.Get("/user/:userID/avatar", pages.HandleUserAvatar)
app.Get("/gallery/:postID", pages.HandlePost)
app.Get("/gallery/:postID/embed", pages.HandleEmbed)
app.Get("/:postID.gifv", pages.HandleGifv)
app.Get("/:baseName.:extension", pages.HandleMedia)
app.Get("/stack/:baseName.:extension", pages.HandleMedia)
app.Get("/:postID", pages.HandlePost)
app.Get("/:postID/embed", pages.HandleEmbed)
err := app.Listen(utils.Config.Addr + ":" + utils.Config.Port)
addr := utils.Config.Addr + ":" + utils.Config.Port
fmt.Println("listening on " + addr)
err := http.ListenAndServe(addr, app)
if err != nil {
fmt.Println(err)
}

View File

@@ -1,10 +1,10 @@
{
"devDependencies": {
"@tailwindcss/cli": "^4.0.17",
"tailwindcss": "^4.0.17"
"@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"
}
"build": "tailwindcss -i static/tailwind.css -o static/app.css",
"watch": "tailwindcss -i static/tailwind.css -o static/app.css --watch"
}
}

View File

@@ -1,22 +1,21 @@
package pages
import (
"os"
"net/http"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleAbout(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=31557600")
w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
func HandleAbout(c *fiber.Ctx) error {
utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=31557600")
c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
return c.Render("about", fiber.Map{
"proto": c.Protocol(),
"domain": c.Hostname(),
"force_webp": os.Getenv("FORCE_WEBP"),
return render.Render(w, "about", map[string]any{
"proto": r.Proto,
"domain": r.Host,
"force_webp": utils.Config.ForceWebp,
})
}

View File

@@ -8,5 +8,5 @@ import (
var ApiClient *api.Client
func InitializeApiClient() {
ApiClient = api.NewClient(utils.Config.ImgurId)
ApiClient = api.NewClient(utils.Config.ImgurId)
}

View File

@@ -1,48 +1,49 @@
package pages
import (
"net/http"
"strings"
"codeberg.org/rimgo/rimgo/api"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleEmbed(c *fiber.Ctx) error {
utils.SetHeaders(c)
c.Set("Cache-Control", "public,max-age=31557600")
c.Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content")
func HandleEmbed(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("Cache-Control", "public,max-age=31557600")
w.Header().Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content")
post, err := api.Album{}, error(nil)
switch {
case strings.HasPrefix(c.Path(), "/a"):
post, err = ApiClient.FetchAlbum(c.Params("postID"))
case strings.HasPrefix(c.Path(), "/gallery"):
post, err = ApiClient.FetchPosts(c.Params("postID"))
case strings.HasPrefix(r.URL.Path, "/a"):
post, err = ApiClient.FetchAlbum(r.PathValue("postID"))
case strings.HasPrefix(r.URL.Path, "/gallery"):
post, err = ApiClient.FetchPosts(r.PathValue("postID"))
default:
post, err = ApiClient.FetchMedia(c.Params("postID"))
post, err = ApiClient.FetchMedia(r.PathValue("postID"))
}
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") {
return utils.RenderError(c, 404)
return utils.RenderError(w, r, 404)
}
if err != nil {
if err != nil {
return err
}
return c.Render("embed", fiber.Map{
return render.Render(w, "embed", map[string]any{
"post": post,
})
}
func HandleGifv(c *fiber.Ctx) error {
utils.SetHeaders(c)
c.Set("Cache-Control", "public,max-age=31557600")
c.Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content")
func HandleGifv(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("Cache-Control", "public,max-age=31557600")
w.Header().Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content")
return c.Render("gifv", fiber.Map{
"id": c.Params("postID"),
return render.Render(w, "gifv", map[string]any{
"id": r.PathValue("postID"),
})
}

View File

@@ -1,19 +1,21 @@
package pages
import (
"net/http"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
var VersionInfo string
func HandleFrontpage(c *fiber.Ctx) error {
utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=31557600")
c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
func HandleFrontpage(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=31557600")
w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
return c.Render("frontpage", fiber.Map{
return render.Render(w, "frontpage", map[string]any{
"config": utils.Config,
"version": VersionInfo,
})

View File

@@ -1,47 +1,61 @@
package pages
import (
"io"
"mime"
"net/http"
"os"
"strings"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleMedia(c *fiber.Ctx) error {
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") {
return handleMedia(c, "https://i.stack.imgur.com/"+strings.ReplaceAll(c.Params("baseName"), "stack/", "")+"."+c.Params("extension"))
func HandleMedia(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Cache-Control", "public,max-age=31557600")
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'")
splitName := strings.SplitN(r.PathValue("component"), ".", 2)
baseName, extension := splitName[0], splitName[1]
if strings.HasPrefix(r.URL.Path, "/stack") {
return handleMedia(w, r, "https://i.stack.imgur.com/"+strings.ReplaceAll(baseName, "stack/", "")+"."+extension)
} else {
return handleMedia(c, "https://i.imgur.com/"+c.Params("baseName")+"."+c.Params("extension"))
return handleMedia(w, r, "https://i.imgur.com/"+baseName+"."+extension)
}
}
func HandleUserCover(c *fiber.Ctx) error {
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")
func HandleUserCover(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().Set("Content-Security-Policy", "default-src 'none'")
return handleMedia(w, r, "https://imgur.com/user/"+r.PathValue("userID")+"/cover?maxwidth=2560")
}
func HandleUserAvatar(c *fiber.Ctx) error {
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")
func HandleUserAvatar(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().Set("Content-Security-Policy", "default-src 'none'")
return handleMedia(w, r, "https://imgur.com/user/"+r.PathValue("userID")+"/avatar")
}
func handleMedia(c *fiber.Ctx, url string) error {
utils.SetHeaders(c)
func handleMedia(w http.ResponseWriter, r *http.Request, url string) error {
utils.SetHeaders(w)
if !utils.Config.RestrictiveCORS {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
path := r.URL.Path
if os.Getenv("FORCE_WEBP") == "1" && c.Query("no_webp") == "" && c.Accepts("image/webp") == "image/webp" && !strings.HasPrefix(c.Path(), "/stack") {
if utils.Config.ForceWebp &&
!strings.HasSuffix(path, ".webp") &&
r.Header.Get("Sec-Fetch-Dest") == "image" &&
r.URL.Query().Get("no_webp") == "" &&
utils.Accepts(r, "image/webp") &&
!strings.HasPrefix(path, "/stack") {
url = strings.ReplaceAll(url, ".png", ".webp")
url = strings.ReplaceAll(url, ".jpg", ".webp")
url = strings.ReplaceAll(url, ".jpeg", ".webp")
filename := strings.TrimPrefix(path, "/")
w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename*": filename}))
}
if strings.HasPrefix(c.Path(), "/stack") && strings.Contains(c.OriginalURL(), "?") {
url = url + "?" + strings.Split(c.OriginalURL(), "?")[1]
queryStr := r.URL.Query().Encode()
if strings.HasPrefix(path, "/stack") && queryStr != "" {
url = url + "?" + queryStr
}
req, err := http.NewRequest("GET", url, nil)
@@ -51,8 +65,9 @@ func handleMedia(c *fiber.Ctx, url string) error {
utils.SetReqHeaders(req)
if c.Get("Range") != "" {
req.Header.Set("Range", c.Get("Range"))
rng := r.URL.Query().Get("Range")
if rng != "" {
req.Header.Set("Range", rng)
}
res, err := http.DefaultClient.Do(req)
@@ -61,17 +76,18 @@ func handleMedia(c *fiber.Ctx, url string) error {
}
if res.StatusCode == 404 || strings.Contains(res.Request.URL.String(), "error/404") {
return utils.RenderError(c, 404)
return utils.RenderError(w, r, 404)
} else if res.StatusCode == 429 {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
c.Set("Accept-Ranges", "bytes")
c.Set("Content-Type", res.Header.Get("Content-Type"))
c.Set("Content-Length", res.Header.Get("Content-Length"))
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Type", res.Header.Get("Content-Type"))
w.Header().Set("Content-Length", res.Header.Get("Content-Length"))
if res.Header.Get("Content-Range") != "" {
c.Set("Content-Range", res.Header.Get("Content-Range"))
w.Header().Set("Content-Range", res.Header.Get("Content-Range"))
}
return c.SendStream(res.Body)
_, err = io.Copy(w, res.Body)
return err
}

View File

@@ -3,12 +3,13 @@ package pages
import (
"crypto/rand"
"fmt"
"net/http"
"strconv"
"strings"
"codeberg.org/rimgo/rimgo/api"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
// Cursed function
@@ -33,31 +34,31 @@ func nextInTag(client *api.Client, tagname, sort, page, I string) string {
return tag.Posts[i+1].Link
}
func HandlePost(c *fiber.Ctx) error {
utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY")
func HandlePost(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
postId := c.Params("postID")
postId := r.PathValue("postID")
if strings.Contains(postId, "-") {
postId = postId[len(postId)-7:]
}
post, err := api.Album{}, error(nil)
switch {
case strings.HasPrefix(c.Path(), "/a"):
case strings.HasPrefix(r.URL.Path, "/a"):
post, err = ApiClient.FetchAlbum(postId)
case strings.HasPrefix(c.Path(), "/gallery"):
case strings.HasPrefix(r.URL.Path, "/gallery"):
post, err = ApiClient.FetchPosts(postId)
case strings.HasPrefix(c.Path(), "/t"):
case strings.HasPrefix(r.URL.Path, "/t"):
post, err = ApiClient.FetchPosts(postId)
default:
post, err = ApiClient.FetchMedia(postId)
}
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") {
return utils.RenderError(c, 404)
return utils.RenderError(w, r, 404)
}
if err != nil {
return err
@@ -65,13 +66,13 @@ func HandlePost(c *fiber.Ctx) error {
comments := []api.Comment{}
if post.SharedWithCommunity {
c.Set("Cache-Control", "public,max-age=604800")
w.Header().Set("Cache-Control", "public,max-age=604800")
comments, err = ApiClient.FetchComments(postId)
if err != nil {
return err
}
} else {
c.Set("Cache-Control", "public,max-age=31557600")
w.Header().Set("Cache-Control", "public,max-age=31557600")
}
nonce := ""
@@ -82,16 +83,16 @@ func HandlePost(c *fiber.Ctx) error {
nonce = fmt.Sprintf("%x", b)
csp = csp + " 'nonce-" + nonce + "'"
}
c.Set("Content-Security-Policy", csp)
w.Header().Set("Content-Security-Policy", csp)
var next string
tagParam := strings.Split(c.Query("tag"), ".")
tagParam := strings.Split(r.URL.Query().Get("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 render.Render(w, "post", map[string]any{
"post": post,
"next": next,
"comments": comments,

View File

@@ -1,17 +1,18 @@
package pages
import (
"github.com/gofiber/fiber/v2"
"net/http"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
)
func HandlePrivacy(c *fiber.Ctx) error {
utils.SetHeaders(c)
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")
func HandlePrivacy(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().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 render.Render(w, "privacy", map[string]any{
"config": utils.Config,
"version": VersionInfo,
})

View File

@@ -1,49 +1,54 @@
package pages
import (
"mime"
"net/http"
"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)
func HandleTagRSS(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
tag, err := ApiClient.FetchTag(c.Params("tag"), c.Query("sort"), "1")
tag, err := ApiClient.FetchTag(r.PathValue("tag"), r.URL.Query().Get("sort"), "1")
if err != nil && err.Error() == "ratelimited by imgur" {
return c.Status(429).SendString("rate limited by imgur")
w.WriteHeader(429)
_, err := w.Write([]byte("rate limited by imgur"))
return err
}
if err != nil {
return err
}
if tag.Display == "" {
return c.Status(404).SendString("tag not found")
w.WriteHeader(404)
_, err := w.Write([]byte("tag not found"))
return err
}
instance := utils.GetInstanceUrl(c)
instance := utils.GetInstanceUrl(r)
feed := &feeds.Feed{
Title: tag.Display + " on Imgur",
Link: &feeds.Link{Href: instance + "/t/" + c.Params("tag")},
Link: &feeds.Link{Href: instance + "/t/" + r.PathValue("tag")},
Created: time.Now(),
}
return handleFeed(c, instance, feed, tag.Posts)
return handleFeed(w, r, instance, feed, tag.Posts)
}
func HandleTrendingRSS(c *fiber.Ctx) error {
utils.SetHeaders(c)
func HandleTrendingRSS(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
section := c.Query("section")
section := r.URL.Query().Get("section")
switch section {
case "hot", "new", "top":
default:
section = "hot"
}
sort := c.Query("sort")
sort := r.URL.Query().Get("sort")
switch sort {
case "newest", "best", "popular":
default:
@@ -55,7 +60,7 @@ func HandleTrendingRSS(c *fiber.Ctx) error {
return err
}
instance := utils.GetInstanceUrl(c)
instance := utils.GetInstanceUrl(r)
feed := &feeds.Feed{
Title: "Trending on Imgur",
@@ -63,24 +68,23 @@ func HandleTrendingRSS(c *fiber.Ctx) error {
Created: time.Now(),
}
return handleFeed(c, instance, feed, results)
return handleFeed(w, r, instance, feed, results)
}
func HandleUserRSS(c *fiber.Ctx) error {
utils.SetHeaders(c)
func HandleUserRSS(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
user := c.Params("userID")
user := r.PathValue("userID")
submissions, err := ApiClient.FetchSubmissions(user, "newest", "1")
if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429)
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
instance := utils.GetInstanceUrl(c)
instance := utils.GetInstanceUrl(r)
feed := &feeds.Feed{
Title: user + " on Imgur",
@@ -88,10 +92,10 @@ func HandleUserRSS(c *fiber.Ctx) error {
Created: time.Now(),
}
return handleFeed(c, instance, feed, submissions)
return handleFeed(w, r, instance, feed, submissions)
}
func handleFeed(c *fiber.Ctx, instance string, feed *feeds.Feed, posts []api.Submission) error {
func handleFeed(w http.ResponseWriter, r *http.Request, instance string, feed *feeds.Feed, posts []api.Submission) error {
feed.Items = []*feeds.Item{}
for _, post := range posts {
@@ -110,27 +114,30 @@ func handleFeed(c *fiber.Ctx, instance string, feed *feeds.Feed, posts []api.Sub
feed.Items = append(feed.Items, item)
}
c.Type(c.Params("type"))
switch c.Params("type") {
w.Header().Set("Content-Type", mime.TypeByExtension("."+r.PathValue("type")))
switch r.PathValue("type") {
case "atom":
body, err := feed.ToAtom()
if err != nil {
return err
}
return c.SendString(body)
w.Write([]byte(body))
case "json":
body, err := feed.ToJSON()
if err != nil {
return err
}
return c.JSON(body)
w.Write([]byte(body))
case "rss":
body, err := feed.ToRss()
if err != nil {
return err
}
return c.SendString(body)
w.Write([]byte(body))
default:
return c.Status(400).SendString("invalid type")
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(400)
w.Write([]byte("invalid type"))
}
return nil
}

View File

@@ -1,30 +1,33 @@
package pages
import (
"net/http"
"strconv"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleSearch(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'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
func HandleSearch(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
query := c.Query("q")
query := r.URL.Query().Get("q")
if utils.ImgurRe.MatchString(query) {
return c.Redirect(utils.ImgurRe.ReplaceAllString(query, ""))
w.Header().Set("Location", utils.ImgurRe.ReplaceAllString(query, ""))
w.WriteHeader(302)
return nil
}
page := "0"
if c.Query("page") != "" {
page = c.Query("page")
page := r.URL.Query().Get("page")
if page == "" {
page = "0"
}
pageNumber, err := strconv.Atoi(c.Query("page"))
pageNumber, err := strconv.Atoi(page)
if err != nil {
pageNumber = 0
}
@@ -34,11 +37,11 @@ func HandleSearch(c *fiber.Ctx) error {
return err
}
return c.Render("search", fiber.Map{
"query": query,
"results": results,
"page": pageNumber,
"nextPage": pageNumber + 1,
"prevPage": pageNumber - 1,
return render.Render(w, "search", map[string]any{
"query": query,
"results": results,
"page": pageNumber,
"nextPage": pageNumber + 1,
"prevPage": pageNumber - 1,
})
}

View File

@@ -1,40 +1,41 @@
package pages
import (
"net/http"
"strconv"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleTag(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'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
func HandleTag(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
page := "1"
if c.Query("page") != "" {
page = c.Query("page")
page := r.URL.Query().Get("page")
if page == "" {
page = "1"
}
pageNumber, err := strconv.Atoi(c.Query("page"))
pageNumber, err := strconv.Atoi(page)
if err != nil {
pageNumber = 0
}
tag, err := ApiClient.FetchTag(c.Params("tag"), c.Query("sort"), page)
tag, err := ApiClient.FetchTag(r.PathValue("tag"), r.URL.Query().Get("sort"), page)
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
if tag.Display == "" {
return utils.RenderError(c, 404)
return utils.RenderError(w, r, 404)
}
return c.Render("tag", fiber.Map{
return render.Render(w, "tag", map[string]any{
"tag": tag,
"page": page,
"nextPage": pageNumber + 1,

View File

@@ -1,35 +1,36 @@
package pages
import (
"net/http"
"strconv"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleTrending(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'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
func HandleTrending(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content")
page := "1"
if c.Query("page") != "" {
page = c.Query("page")
page := r.URL.Query().Get("page")
if page == "" {
page = "1"
}
pageNumber, err := strconv.Atoi(c.Query("page"))
pageNumber, err := strconv.Atoi(page)
if err != nil {
pageNumber = 1
}
section := c.Query("section")
section := r.URL.Query().Get("section")
switch section {
case "hot", "new", "top":
default:
section = "hot"
}
sort := c.Query("sort")
sort := r.URL.Query().Get("sort")
switch sort {
case "newest", "best", "popular":
default:
@@ -41,12 +42,12 @@ func HandleTrending(c *fiber.Ctx) error {
return err
}
return c.Render("trending", fiber.Map{
"results": results,
"section": section,
"sort": sort,
"page": pageNumber,
"nextPage": pageNumber + 1,
"prevPage": pageNumber - 1,
return render.Render(w, "trending", map[string]any{
"results": results,
"section": section,
"sort": sort,
"page": pageNumber,
"nextPage": pageNumber + 1,
"prevPage": pageNumber - 1,
})
}

View File

@@ -1,49 +1,49 @@
package pages
import (
"net/http"
"strconv"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleUser(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")
func HandleUser(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().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")
page := r.URL.Query().Get("page")
if page == "" {
page = "0"
}
pageNumber, err := strconv.Atoi(c.Query("page"))
pageNumber, err := strconv.Atoi(page)
if err != nil {
pageNumber = 0
}
user, err := ApiClient.FetchUser(c.Params("userID"))
user, err := ApiClient.FetchUser(r.PathValue("userID"))
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
if user.Username == "" {
return utils.RenderError(c, 404)
return utils.RenderError(w, r, 404)
}
submissions, err := ApiClient.FetchSubmissions(c.Params("userID"), "newest", page)
submissions, err := ApiClient.FetchSubmissions(r.PathValue("userID"), "newest", page)
if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429)
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
return c.Render("user", fiber.Map{
return render.Render(w, "user", map[string]any{
"user": user,
"submissions": submissions,
"page": page,
@@ -52,74 +52,73 @@ func HandleUser(c *fiber.Ctx) error {
})
}
func HandleUserComments(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")
func HandleUserComments(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().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")
user, err := ApiClient.FetchUser(c.Params("userID"))
user, err := ApiClient.FetchUser(r.PathValue("userID"))
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
if user.Username == "" {
return utils.RenderError(c, 404)
return utils.RenderError(w, r, 404)
}
comments, err := ApiClient.FetchUserComments(c.Params("userID"))
comments, err := ApiClient.FetchUserComments(r.PathValue("userID"))
if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429)
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
return c.Render("userComments", fiber.Map{
return render.Render(w, "userComments", map[string]any{
"user": user,
"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")
func HandleUserFavorites(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(w)
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Cache-Control", "public,max-age=604800")
w.Header().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")
page := r.URL.Query().Get("page")
if page == "" {
page = "0"
}
pageNumber, err := strconv.Atoi(c.Query("page"))
pageNumber, err := strconv.Atoi(page)
if err != nil {
pageNumber = 0
}
user, err := ApiClient.FetchUser(c.Params("userID"))
user, err := ApiClient.FetchUser(r.PathValue("userID"))
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
if user.Username == "" {
return utils.RenderError(c, 404)
return utils.RenderError(w, r, 404)
}
favorites, err := ApiClient.FetchUserFavorites(c.Params("userID"), "newest", page)
favorites, err := ApiClient.FetchUserFavorites(r.PathValue("userID"), "newest", page)
if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429)
return utils.RenderError(w, r, 429)
}
if err != nil {
return err
}
return c.Render("userFavorites", fiber.Map{
return render.Render(w, "userFavorites", map[string]any{
"user": user,
"favorites": favorites,
"page": page,

154
pnpm-lock.yaml generated
View File

@@ -9,11 +9,11 @@ importers:
.:
devDependencies:
'@tailwindcss/cli':
specifier: ^4.0.17
version: 4.0.17
specifier: ^4.1.5
version: 4.1.5
tailwindcss:
specifier: ^4.0.17
version: 4.0.17
specifier: ^4.1.5
version: 4.1.5
packages:
@@ -99,81 +99,93 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@tailwindcss/cli@4.0.17':
resolution: {integrity: sha512-Jygu5jjf64vzNXeTr00OhlMzRq+/KwNxJS6eZlgcBpEbXTEmmlr/PSjv1Q9Lk3aTnQc4yNlXkHdWPnlpF+ILUg==}
'@tailwindcss/cli@4.1.5':
resolution: {integrity: sha512-Kr567rDwDjY1VUnfqh5/+DCpRf4B8lPs5O9flP4kri7n4AM2aubrIxGSh5GN8s+awUKw/U4+6kNlEnZbBNfUeg==}
hasBin: true
'@tailwindcss/node@4.0.17':
resolution: {integrity: sha512-LIdNwcqyY7578VpofXyqjH6f+3fP4nrz7FBLki5HpzqjYfXdF2m/eW18ZfoKePtDGg90Bvvfpov9d2gy5XVCbg==}
'@tailwindcss/node@4.1.5':
resolution: {integrity: sha512-CBhSWo0vLnWhXIvpD0qsPephiaUYfHUX3U9anwDaHZAeuGpTiB3XmsxPAN6qX7bFhipyGBqOa1QYQVVhkOUGxg==}
'@tailwindcss/oxide-android-arm64@4.0.17':
resolution: {integrity: sha512-3RfO0ZK64WAhop+EbHeyxGThyDr/fYhxPzDbEQjD2+v7ZhKTb2svTWy+KK+J1PHATus2/CQGAGp7pHY/8M8ugg==}
'@tailwindcss/oxide-android-arm64@4.1.5':
resolution: {integrity: sha512-LVvM0GirXHED02j7hSECm8l9GGJ1RfgpWCW+DRn5TvSaxVsv28gRtoL4aWKGnXqwvI3zu1GABeDNDVZeDPOQrw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.0.17':
resolution: {integrity: sha512-e1uayxFQCCDuzTk9s8q7MC5jFN42IY7nzcr5n0Mw/AcUHwD6JaBkXnATkD924ZsHyPDvddnusIEvkgLd2CiREg==}
'@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.0.17':
resolution: {integrity: sha512-d6z7HSdOKfXQ0HPlVx1jduUf/YtBuCCtEDIEFeBCzgRRtDsUuRtofPqxIVaSCUTOk5+OfRLonje6n9dF6AH8wQ==}
'@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.0.17':
resolution: {integrity: sha512-EjrVa6lx3wzXz3l5MsdOGtYIsRjgs5Mru6lDv4RuiXpguWeOb3UzGJ7vw7PEzcFadKNvNslEQqoAABeMezprxQ==}
'@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.0.17':
resolution: {integrity: sha512-65zXfCOdi8wuaY0Ye6qMR5LAXokHYtrGvo9t/NmxvSZtCCitXV/gzJ/WP5ksXPhff1SV5rov0S+ZIZU+/4eyCQ==}
'@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.0.17':
resolution: {integrity: sha512-+aaq6hJ8ioTdbJV5IA1WjWgLmun4T7eYLTvJIToiXLHy5JzUERRbIZjAcjgK9qXMwnvuu7rqpxzej+hGoEcG5g==}
'@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.0.17':
resolution: {integrity: sha512-/FhWgZCdUGAeYHYnZKekiOC0aXFiBIoNCA0bwzkICiMYS5Rtx2KxFfMUXQVnl4uZRblG5ypt5vpPhVaXgGk80w==}
'@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.0.17':
resolution: {integrity: sha512-gELJzOHK6GDoIpm/539Golvk+QWZjxQcbkKq9eB2kzNkOvrP0xc5UPgO9bIMNt1M48mO8ZeNenCMGt6tfkvVBg==}
'@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.0.17':
resolution: {integrity: sha512-68NwxcJrZn94IOW4TysMIbYv5AlM6So1luTlbYUDIGnKma1yTFGBRNEJ+SacJ3PZE2rgcTBNRHX1TB4EQ/XEHw==}
'@tailwindcss/oxide-linux-x64-musl@4.1.5':
resolution: {integrity: sha512-6UbBBplywkk/R+PqqioskUeXfKcBht3KU7juTi1UszJLx0KPXUo10v2Ok04iBJIaDPkIFkUOVboXms5Yxvaz+g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-win32-arm64-msvc@4.0.17':
resolution: {integrity: sha512-AkBO8efP2/7wkEXkNlXzRD4f/7WerqKHlc6PWb5v0jGbbm22DFBLbIM19IJQ3b+tNewQZa+WnPOaGm0SmwMNjw==}
'@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.0.17':
resolution: {integrity: sha512-7/DTEvXcoWlqX0dAlcN0zlmcEu9xSermuo7VNGX9tJ3nYMdo735SHvbrHDln1+LYfF6NhJ3hjbpbjkMOAGmkDg==}
'@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.0.17':
resolution: {integrity: sha512-B4OaUIRD2uVrULpAD1Yksx2+wNarQr2rQh65nXqaqbLY1jCd8fO+3KLh/+TH4Hzh2NTHQvgxVbPdUDOtLk7vAw==}
'@tailwindcss/oxide@4.1.5':
resolution: {integrity: sha512-1n4br1znquEvyW/QuqMKQZlBen+jxAbvyduU87RS8R3tUSvByAkcaMTkJepNIrTlYhD+U25K4iiCIxE6BGdRYA==}
engines: {node: '>= 10'}
braces@3.0.3:
@@ -185,8 +197,8 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
enhanced-resolve@5.18.1:
@@ -298,8 +310,8 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
tailwindcss@4.0.17:
resolution: {integrity: sha512-OErSiGzRa6rLiOvaipsDZvLMSpsBZ4ysB4f0VKGXUrjw2jfkJRd6kjRKV2+ZmTCNvwtvgdDam5D7w6WXsdLJZw==}
tailwindcss@4.1.5:
resolution: {integrity: sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==}
tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
@@ -371,69 +383,73 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
'@tailwindcss/cli@4.0.17':
'@tailwindcss/cli@4.1.5':
dependencies:
'@parcel/watcher': 2.5.1
'@tailwindcss/node': 4.0.17
'@tailwindcss/oxide': 4.0.17
'@tailwindcss/node': 4.1.5
'@tailwindcss/oxide': 4.1.5
enhanced-resolve: 5.18.1
lightningcss: 1.29.2
mri: 1.2.0
picocolors: 1.1.1
tailwindcss: 4.0.17
tailwindcss: 4.1.5
'@tailwindcss/node@4.0.17':
'@tailwindcss/node@4.1.5':
dependencies:
enhanced-resolve: 5.18.1
jiti: 2.4.2
tailwindcss: 4.0.17
lightningcss: 1.29.2
tailwindcss: 4.1.5
'@tailwindcss/oxide-android-arm64@4.0.17':
'@tailwindcss/oxide-android-arm64@4.1.5':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.0.17':
'@tailwindcss/oxide-darwin-arm64@4.1.5':
optional: true
'@tailwindcss/oxide-darwin-x64@4.0.17':
'@tailwindcss/oxide-darwin-x64@4.1.5':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.0.17':
'@tailwindcss/oxide-freebsd-x64@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.0.17':
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.0.17':
'@tailwindcss/oxide-linux-arm64-gnu@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.0.17':
'@tailwindcss/oxide-linux-arm64-musl@4.1.5':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.0.17':
'@tailwindcss/oxide-linux-x64-gnu@4.1.5':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.0.17':
'@tailwindcss/oxide-linux-x64-musl@4.1.5':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.0.17':
'@tailwindcss/oxide-wasm32-wasi@4.1.5':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.0.17':
'@tailwindcss/oxide-win32-arm64-msvc@4.1.5':
optional: true
'@tailwindcss/oxide@4.0.17':
'@tailwindcss/oxide-win32-x64-msvc@4.1.5':
optional: true
'@tailwindcss/oxide@4.1.5':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.0.17
'@tailwindcss/oxide-darwin-arm64': 4.0.17
'@tailwindcss/oxide-darwin-x64': 4.0.17
'@tailwindcss/oxide-freebsd-x64': 4.0.17
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.17
'@tailwindcss/oxide-linux-arm64-gnu': 4.0.17
'@tailwindcss/oxide-linux-arm64-musl': 4.0.17
'@tailwindcss/oxide-linux-x64-gnu': 4.0.17
'@tailwindcss/oxide-linux-x64-musl': 4.0.17
'@tailwindcss/oxide-win32-arm64-msvc': 4.0.17
'@tailwindcss/oxide-win32-x64-msvc': 4.0.17
'@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:
@@ -441,7 +457,7 @@ snapshots:
detect-libc@1.0.3: {}
detect-libc@2.0.3: {}
detect-libc@2.0.4: {}
enhanced-resolve@5.18.1:
dependencies:
@@ -496,7 +512,7 @@ snapshots:
lightningcss@1.29.2:
dependencies:
detect-libc: 2.0.3
detect-libc: 2.0.4
optionalDependencies:
lightningcss-darwin-arm64: 1.29.2
lightningcss-darwin-x64: 1.29.2
@@ -522,7 +538,7 @@ snapshots:
picomatch@2.3.1: {}
tailwindcss@4.0.17: {}
tailwindcss@4.1.5: {}
tapable@2.2.1: {}

17
render/helpers.go Normal file
View File

@@ -0,0 +1,17 @@
package render
import "github.com/mailgun/raymond/v2"
func (r *renderer) registerHelpers() {
funcmap := map[string]any{
"noteq": noteq,
}
raymond.RegisterHelpers(funcmap)
}
func noteq(a, b any, options *raymond.Options) any {
if raymond.Str(a) != raymond.Str(b) {
return options.Fn()
}
return ""
}

76
render/render.go Normal file
View File

@@ -0,0 +1,76 @@
// stolen from gofiber/template but simpler
package render
import (
"fmt"
"io"
"io/fs"
"path/filepath"
"strings"
"github.com/mailgun/raymond/v2"
)
var Renderer *renderer
func Render(out io.Writer, name string, bind map[string]any) error {
return Renderer.Render(out, name, bind)
}
type renderer struct {
templates map[string]*raymond.Template
}
const ext = ".hbs"
func Initialize(views fs.FS) {
r := new(renderer)
r.templates = make(map[string]*raymond.Template)
r.registerHelpers()
fs.WalkDir(views, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
name, hasExt := strings.CutSuffix(path, ext)
if !hasExt {
return nil
}
path = filepath.ToSlash(path)
file, err := views.Open(path)
if err != nil {
return err
}
defer file.Close()
buf, err := io.ReadAll(file)
if err != nil {
return err
}
tmpl, err := raymond.Parse(string(buf))
if err != nil {
return err
}
r.templates[name] = tmpl
return nil
})
for j := range r.templates {
for n, template := range r.templates {
r.templates[j].RegisterPartialTemplate(n, template)
}
}
Renderer = r
}
func (r *renderer) Render(out io.Writer, name string, bind map[string]any) error {
tmpl := r.templates[name]
if tmpl == nil {
return fmt.Errorf("render: template %s does not exist", name)
}
parsed, err := tmpl.Exec(bind)
if err != nil {
return fmt.Errorf("render: %w", err)
}
if _, err = out.Write([]byte(parsed)); err != nil {
return fmt.Errorf("render: %w", err)
}
return err
}

22
utils/accepts.go Normal file
View File

@@ -0,0 +1,22 @@
package utils
import (
"net/http"
"strings"
)
func Accepts(r *http.Request, format string) bool {
format = strings.ToLower(format)
group := strings.Split(format, "/")[0] + "/*"
header := r.Header.Get("Accept")
if header == "" {
return false
}
for _, mime := range strings.Split(header, ",") {
mime = strings.ToLower(strings.TrimSpace(strings.SplitN(mime, ";", 2)[0]))
if mime == "*/*" || mime == format || mime == group {
return true
}
}
return false
}

View File

@@ -6,62 +6,53 @@ import (
)
type config struct {
Port string
Addr string
ImgurId string
Secure bool
FiberPrefork bool
ImageCache bool
CleanupInterval time.Duration
CacheDir string
Privacy map[string]interface{}
Port string
Addr string
ImgurId string
ProtocolDetection bool
Secure bool
ForceWebp bool
RestrictiveCORS bool
ImageCache bool
CleanupInterval time.Duration
CacheDir string
Privacy map[string]interface{}
}
var Config config
func envString(name, def string) string {
env := os.Getenv(name)
if env != "" {
return env
}
return def
}
func envBool(name string) bool {
return os.Getenv(name) == "true" || os.Getenv(name) == "1"
}
func LoadConfig() {
port := "3000"
if os.Getenv("PORT") != "" {
port = os.Getenv("PORT")
}
if os.Getenv("RIMGU_PORT") != "" {
port = os.Getenv("RIMGU_PORT")
}
addr := "0.0.0.0"
if os.Getenv("ADDRESS") != "" {
addr = os.Getenv("ADDRESS")
}
if os.Getenv("RIMGU_ADDRESS") != "" {
addr = os.Getenv("RIMGU_ADDRESS")
}
imgurId := "546c25a59c58ad7"
if os.Getenv("IMGUR_CLIENT_ID") != "" {
imgurId = os.Getenv("IMGUR_CLIENT_ID")
}
if os.Getenv("RIMGU_IMGUR_CLIENT_ID") != "" {
imgurId = os.Getenv("RIMGU_IMGUR_CLIENT_ID")
}
Config = config{
Port: port,
Addr: addr,
ImgurId: imgurId,
Secure: os.Getenv("SECURE") == "true",
FiberPrefork: os.Getenv("FIBER_PREFORK") == "true",
Port: envString("PORT", "3000"),
Addr: envString("ADDR", "0.0.0.0"),
ImgurId: envString("IMGUR_CLIENT_ID", "546c25a59c58ad7"),
ProtocolDetection: envBool("PROTOCOL_DETECTION"),
Secure: envBool("SECURE"),
ForceWebp: envBool("FORCE_WEBP"),
RestrictiveCORS: envBool("RESTRICTIVE_CORS"),
Privacy: map[string]interface{}{
"set": os.Getenv("PRIVACY_NOT_COLLECTED") != "",
"policy": os.Getenv("PRIVACY_POLICY"),
"message": os.Getenv("PRIVACY_MESSAGE"),
"country": os.Getenv("PRIVACY_COUNTRY"),
"provider": os.Getenv("PRIVACY_PROVIDER"),
"cloudflare": os.Getenv("PRIVACY_CLOUDFLARE") == "true",
"not_collected": os.Getenv("PRIVACY_NOT_COLLECTED") == "true",
"ip": os.Getenv("PRIVACY_IP") == "true",
"url": os.Getenv("PRIVACY_URL") == "true",
"device": os.Getenv("PRIVACY_DEVICE") == "true",
"diagnostics": os.Getenv("PRIVACY_DIAGNOSTICS") == "true",
"cloudflare": envBool("PRIVACY_CLOUDFLARE"),
"not_collected": envBool("PRIVACY_NOT_COLLECTED"),
"ip": envBool("PRIVACY_IP"),
"url": envBool("PRIVACY_URL"),
"device": envBool("PRIVACY_DEVICE"),
"diagnostics": envBool("PRIVACY_DIAGNOSTICS"),
},
}
}

View File

@@ -1,25 +1,43 @@
package utils
import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"codeberg.org/rimgo/rimgo/render"
"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)
func RenderError(w http.ResponseWriter, r *http.Request, code int, str ...string) (err error) {
if len(str) != 1 {
str = []string{""}
}
codeStr := "generic"
if code == 0 {
code = 500
}
if code != 500 {
codeStr = strconv.Itoa(code)
}
if !Accepts(r, "text/html") && r.PathValue("extension") != "" {
w.Header().Set("Content-Type", "image/png")
w.WriteHeader(code)
file, _ := static.GetFiles().Open("img/error-" + codeStr + ".png")
defer file.Close()
_, err = io.Copy(w, file)
} else {
return c.Status(code).Render("errors/" + strconv.Itoa(code), fiber.Map{
"path": c.Path(),
w.WriteHeader(code)
err = render.Render(w, "errors/"+codeStr, map[string]any{
"path": r.URL.Path,
"err": str[0],
})
}
if err != nil {
// don't panic or return error, it will loop
fmt.Println("error in RenderError: " + err.Error())
}
return nil
}

View File

@@ -1,11 +1,21 @@
package utils
import "github.com/gofiber/fiber/v2"
import "net/http"
func GetInstanceUrl(c *fiber.Ctx) string {
proto := "https://"
func GetInstanceProtocol(r *http.Request) string {
proto := "https"
if !Config.Secure {
proto = "http://"
proto = "http"
}
return proto + c.Hostname()
if Config.ProtocolDetection {
xproto := r.Header.Get("X-Forwarded-Proto")
if xproto != "" {
proto = xproto
}
}
return proto
}
func GetInstanceUrl(r *http.Request) string {
return GetInstanceProtocol(r) + "://" + r.Host
}

View File

@@ -35,7 +35,7 @@ func GetJSON(url string) (gjson.Result, error) {
return gjson.Result{}, err
}
switch (res.StatusCode) {
switch res.StatusCode {
case 200:
return gjson.Parse(string(body)), nil
case 429:

View File

@@ -2,16 +2,14 @@ package utils
import (
"net/http"
"github.com/gofiber/fiber/v2"
)
func SetHeaders(c *fiber.Ctx) {
c.Set("Referrer-Policy", "no-referrer")
c.Set("X-Content-Type-Options", "nosniff")
c.Set("X-Robots-Tag", "noindex, noimageindex, nofollow")
c.Set("Strict-Transport-Security", "max-age=31557600")
c.Set("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(self), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()")
func SetHeaders(w http.ResponseWriter) {
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Robots-Tag", "noindex, noimageindex, nofollow")
w.Header().Set("Strict-Transport-Security", "max-age=31557600")
w.Header().Set("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(self), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()")
}
func SetReqHeaders(req *http.Request) {

16
utils/splitNameExt.go Normal file
View File

@@ -0,0 +1,16 @@
package utils
func SplitNameExt(path string) (name, ext string) {
name, ext = path, ""
for range 5 {
if len(name) == 0 || name[len(name)-1] == '.' || name[len(name)-1] == '/' {
break
}
name = name[:len(name)-1]
ext = path[len(name):]
}
if len(name) == 0 || name[len(name)-1] != '.' {
return path, ""
}
return
}