mirror of
https://codeberg.org/video-prize-ranch/rimgo.git
synced 2026-02-15 04:55:59 +00:00
Compare commits
15 Commits
v1.4.1
...
encoding-j
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e241d35efe | ||
|
|
02be603dcc | ||
|
|
c208a55f40 | ||
|
|
4441d25d38 | ||
|
|
975ffa0b9c | ||
|
|
7b1314fae3 | ||
|
|
fd704f53e7 | ||
|
|
bf849e1cbc | ||
|
|
cd4a36c9f7 | ||
|
|
04fbc7f5f4 | ||
|
|
189ebeefde | ||
|
|
3f40c25b04 | ||
|
|
33fa04e98d | ||
|
|
e5b87dc924 | ||
|
|
8cb2524924 |
@@ -5,7 +5,6 @@ import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/rimgo/rimgo/utils"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
@@ -101,17 +100,13 @@ func parseAlbum(data gjson.Result) (Album, error) {
|
||||
url := value.Get("url").String()
|
||||
url = strings.ReplaceAll(url, "https://i.imgur.com", "")
|
||||
|
||||
description := value.Get("metadata.description").String()
|
||||
description = strings.ReplaceAll(description, "\n", "<br>")
|
||||
description = bluemonday.UGCPolicy().Sanitize(description)
|
||||
|
||||
media = append(media, Media{
|
||||
Id: value.Get("id").String(),
|
||||
Name: value.Get("name").String(),
|
||||
MimeType: value.Get("mime_type").String(),
|
||||
Type: value.Get("type").String(),
|
||||
Title: value.Get("metadata.title").String(),
|
||||
Description: description,
|
||||
Description: value.Get("metadata.description").String(),
|
||||
Url: url,
|
||||
})
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ type Client struct {
|
||||
Cache *cache.Cache
|
||||
}
|
||||
|
||||
func NewClient(clientId string) (*Client) {
|
||||
func NewClient(clientId string) *Client {
|
||||
client := Client{
|
||||
ClientID: clientId,
|
||||
Cache: cache.New(15*time.Minute, 15*time.Minute),
|
||||
|
||||
189
api/comments.go
189
api/comments.go
@@ -1,145 +1,114 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"codeberg.org/rimgo/rimgo/utils"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/tidwall/gjson"
|
||||
"gitlab.com/golang-commonmark/linkify"
|
||||
)
|
||||
|
||||
type Comment struct {
|
||||
Comments []Comment
|
||||
User User
|
||||
Post Submission
|
||||
Id string
|
||||
Comment string
|
||||
Upvotes int64
|
||||
Downvotes int64
|
||||
Platform string
|
||||
CreatedAt string
|
||||
RelTime string
|
||||
UpdatedAt string
|
||||
DeletedAt string
|
||||
ID int `json:"id"`
|
||||
ParentID int `json:"parent_id"`
|
||||
Comment string `json:"comment"`
|
||||
PostID string `json:"post_id"`
|
||||
UpvoteCount int `json:"upvote_count"`
|
||||
DownvoteCount int `json:"downvote_count"`
|
||||
PointCount int `json:"point_count"`
|
||||
Vote struct{} `json:"vote"` // ???
|
||||
PlatformID int `json:"platform_id"`
|
||||
Platform string `json:"platform"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt time.Time `json:"deleted_at"`
|
||||
Next struct{} `json:"next"` // ???
|
||||
Comments commentArray `json:"comments"`
|
||||
AccountID int `json:"account_id"`
|
||||
Account _ApiUser `json:"account"`
|
||||
Post commentPost `json:"post"`
|
||||
}
|
||||
|
||||
type _ApiUser struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
|
||||
func (client *Client) FetchComments(galleryID string) ([]Comment, error) {
|
||||
cacheData, found := client.Cache.Get(galleryID + "-comments")
|
||||
if found {
|
||||
return cacheData.([]Comment), nil
|
||||
return cacheData.(commentArray), nil
|
||||
}
|
||||
|
||||
data, err := utils.GetJSON("https://api.imgur.com/comment/v1/comments?client_id=" + client.ClientID + "&filter[post]=eq:" + galleryID + "&include=account,adconfig&per_page=30&sort=best")
|
||||
data, err := utils.GetJSONNew("https://api.imgur.com/comment/v1/comments?client_id=" + client.ClientID + "&filter[post]=eq:" + galleryID + "&include=account,adconfig&per_page=30&sort=best")
|
||||
if err != nil {
|
||||
return []Comment{}, nil
|
||||
}
|
||||
|
||||
var parsed commentApiResponse
|
||||
err = json.Unmarshal(data, &parsed)
|
||||
if err != nil {
|
||||
return []Comment{}, err
|
||||
}
|
||||
|
||||
client.Cache.Set(galleryID+"-comments", parsed.Data, cache.DefaultExpiration)
|
||||
return parsed.Data, nil
|
||||
}
|
||||
|
||||
func parseComment(data json.RawMessage, out *Comment) {
|
||||
err := json.Unmarshal(data, &out)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type commentArray []Comment
|
||||
|
||||
func (arr *commentArray) UnmarshalJSON(data []byte) error {
|
||||
var rawArr []json.RawMessage
|
||||
err := json.Unmarshal(data, &rawArr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*arr = make(commentArray, len(rawArr))
|
||||
panics := make([]error, 0, len(rawArr))
|
||||
wg := sync.WaitGroup{}
|
||||
comments := make([]Comment, 0)
|
||||
data.Get("data").ForEach(
|
||||
func(key, value gjson.Result) bool {
|
||||
var handlePanic = func() {
|
||||
if v := recover(); v != nil {
|
||||
v, ok := v.(error)
|
||||
if !ok {
|
||||
v = fmt.Errorf("%v", v)
|
||||
}
|
||||
panics = append(panics, v)
|
||||
}
|
||||
}
|
||||
for i, value := range rawArr {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer handlePanic()
|
||||
defer wg.Done()
|
||||
comments = append(comments, parseComment(value))
|
||||
parseComment(value, &(*arr)[i])
|
||||
}()
|
||||
|
||||
return true
|
||||
},
|
||||
)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
client.Cache.Set(galleryID+"-comments", comments, cache.DefaultExpiration)
|
||||
return comments, nil
|
||||
if len(panics) != 0 {
|
||||
return errors.Join(panics...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var imgurRe = regexp.MustCompile(`https?://imgur\.com/(gallery|a)?/(.*)`)
|
||||
var imgurRe2 = regexp.MustCompile(`https?://imgur\.com/(.*)`)
|
||||
var imgRe = regexp.MustCompile(`https?://i\.imgur\.com/(.*)\.(png|gif|jpe?g|webp)`)
|
||||
var vidRe = regexp.MustCompile(`https?://i\.imgur\.com/(.*)\.(mp4|webm)`)
|
||||
var vidFormatRe = regexp.MustCompile(`\.(mp4|webm)`)
|
||||
var iImgurRe = regexp.MustCompile(`https?://i\.imgur\.com`)
|
||||
type commentPost Submission
|
||||
|
||||
func parseComment(data gjson.Result) Comment {
|
||||
createdTime, _ := time.Parse("2006-01-02T15:04:05Z", data.Get("created_at").String())
|
||||
createdAt := createdTime.Format("January 2, 2006 3:04 PM")
|
||||
updatedAt, _ := utils.FormatDate(data.Get("updated_at").String())
|
||||
deletedAt, _ := utils.FormatDate(data.Get("deleted_at").String())
|
||||
|
||||
userAvatar := strings.ReplaceAll(data.Get("account.avatar").String(), "https://i.imgur.com", "")
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
comments := make([]Comment, 0)
|
||||
data.Get("comments").ForEach(
|
||||
func(key, value gjson.Result) bool {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
comments = append(comments, parseComment(value))
|
||||
}()
|
||||
|
||||
return true
|
||||
},
|
||||
)
|
||||
wg.Wait()
|
||||
|
||||
comment := data.Get("comment").String()
|
||||
|
||||
comment = strings.ReplaceAll(comment, "\n", "<br>")
|
||||
|
||||
for _, match := range imgRe.FindAllString(comment, -1) {
|
||||
img := iImgurRe.ReplaceAllString(match, "")
|
||||
img = `<img src="` + img + `" class="comment__media" loading="lazy"/>`
|
||||
comment = strings.Replace(comment, match, img, 1)
|
||||
func (post *commentPost) UnmarshalJSON(data []byte) error {
|
||||
*post = commentPost(parseSubmission(gjson.Parse(string(data))))
|
||||
return nil
|
||||
}
|
||||
for _, match := range vidRe.FindAllString(comment, -1) {
|
||||
vid := iImgurRe.ReplaceAllString(match, "")
|
||||
vid = `<video class="comment__media" controls loop preload="none" poster="` + vidFormatRe.ReplaceAllString(vid, ".webp") + `"><source type="` + strings.Split(vid, ".")[1] + `" src="` + vid + `" /></video>`
|
||||
comment = strings.Replace(comment, match, vid, 1)
|
||||
}
|
||||
for _, l := range linkify.Links(comment) {
|
||||
origLink := comment[l.Start:l.End]
|
||||
link := `<a href="` + origLink + `">` + origLink + `</a>`
|
||||
comment = strings.Replace(comment, origLink, link, 1)
|
||||
}
|
||||
comment = imgurRe.ReplaceAllString(comment, "/$1/$2")
|
||||
comment = imgurRe2.ReplaceAllString(comment, "/$1")
|
||||
|
||||
p := bluemonday.UGCPolicy()
|
||||
p.AllowImages()
|
||||
p.AllowElements("video", "source")
|
||||
p.AllowAttrs("src", "tvpe").OnElements("source")
|
||||
p.AllowAttrs("controls", "loop", "preload", "poster").OnElements("video")
|
||||
p.AllowAttrs("class", "loading").OnElements("img", "video")
|
||||
p.RequireNoReferrerOnLinks(true)
|
||||
p.RequireNoFollowOnLinks(true)
|
||||
p.RequireCrossOriginAnonymous(true)
|
||||
comment = p.Sanitize(comment)
|
||||
|
||||
return Comment{
|
||||
Comments: comments,
|
||||
User: User{
|
||||
Id: data.Get("account.id").Int(),
|
||||
Username: data.Get("account.username").String(),
|
||||
Avatar: userAvatar,
|
||||
},
|
||||
Post: parseSubmission(data.Get("post")),
|
||||
Id: data.Get("id").String(),
|
||||
Comment: comment,
|
||||
Upvotes: data.Get("upvote_count").Int(),
|
||||
Downvotes: data.Get("downvote_count").Int(),
|
||||
Platform: data.Get("platform").String(),
|
||||
CreatedAt: createdAt,
|
||||
RelTime: humanize.Time(createdTime),
|
||||
UpdatedAt: updatedAt,
|
||||
DeletedAt: deletedAt,
|
||||
}
|
||||
type commentApiResponse struct {
|
||||
Data commentArray `json:"data"`
|
||||
}
|
||||
|
||||
23
api/user.go
23
api/user.go
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -154,7 +155,7 @@ func (client *Client) FetchUserFavorites(username string, sort string, page stri
|
||||
func (client *Client) FetchUserComments(username string) ([]Comment, error) {
|
||||
cacheData, found := client.Cache.Get(username + "-usercomments")
|
||||
if found {
|
||||
return cacheData.([]Comment), nil
|
||||
return cacheData.(commentArray), nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "https://api.imgur.com/comment/v1/comments", nil)
|
||||
@@ -176,23 +177,19 @@ func (client *Client) FetchUserComments(username string) ([]Comment, error) {
|
||||
return []Comment{}, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return []Comment{}, err
|
||||
}
|
||||
|
||||
data := gjson.Parse(string(body))
|
||||
var parsed commentApiResponse
|
||||
err = json.Unmarshal(data, &parsed)
|
||||
if err != nil {
|
||||
return []Comment{}, err
|
||||
}
|
||||
|
||||
comments := make([]Comment, 0)
|
||||
data.Get("data").ForEach(
|
||||
func(key, value gjson.Result) bool {
|
||||
comments = append(comments, parseComment(value))
|
||||
return true
|
||||
},
|
||||
)
|
||||
|
||||
client.Cache.Set(username+"-usercomments", comments, cache.DefaultExpiration)
|
||||
return comments, nil
|
||||
client.Cache.Set(username+"-usercomments", parsed.Data, cache.DefaultExpiration)
|
||||
return parsed.Data, nil
|
||||
}
|
||||
|
||||
func parseSubmission(value gjson.Result) Submission {
|
||||
|
||||
17
go.mod
17
go.mod
@@ -5,8 +5,6 @@ go 1.24.0
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.11.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gofiber/fiber/v2 v2.52.10
|
||||
github.com/gofiber/template/handlebars/v2 v2.1.12
|
||||
github.com/gorilla/feeds v1.2.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mailgun/raymond/v2 v2.0.48
|
||||
@@ -17,26 +15,13 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/gofiber/template v1.8.3 // indirect
|
||||
github.com/gofiber/utils v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // 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.19 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // 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.6.3 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.68.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
|
||||
|
||||
35
go.sum
35
go.sum
@@ -1,57 +1,31 @@
|
||||
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/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
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=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/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.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY=
|
||||
github.com/gofiber/fiber/v2 v2.52.10/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.12 h1:uWBMEnhTxVyarRjyj5uDrNg8rVMGzi6fhsL+PAJBvb0=
|
||||
github.com/gofiber/template/handlebars/v2 v2.1.12/go.mod h1:K3h933a8wPFjIrLRUcnIVPTUW867ND6gqpw0zZ3yKpk=
|
||||
github.com/gofiber/utils v1.2.0 h1:NCaqd+Efg3khhN++eeUUTyBz+byIxAsmIjpl8kKOMIc=
|
||||
github.com/gofiber/utils v1.2.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.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
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.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
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.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
@@ -72,14 +46,6 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
||||
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.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
|
||||
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
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.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
|
||||
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
|
||||
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=
|
||||
@@ -120,7 +86,6 @@ 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=
|
||||
|
||||
224
main.go
224
main.go
@@ -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/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"
|
||||
"github.com/mailgun/raymond/v2"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
pages.RenderError(w, r, 500, fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
err := h(w, r)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
pages.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) {
|
||||
pages.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 utils.GetInstanceUrl(c) + c.OriginalURL()
|
||||
},
|
||||
CacheControl: true,
|
||||
StoreResponseHeaders: true,
|
||||
app.Handle("GET /errors/404", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
pages.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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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 RenderError(w, r, 429)
|
||||
}
|
||||
if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") {
|
||||
return utils.RenderError(c, 404)
|
||||
return RenderError(w, r, 404)
|
||||
}
|
||||
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"),
|
||||
})
|
||||
}
|
||||
44
pages/error.go
Normal file
44
pages/error.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"codeberg.org/rimgo/rimgo/render"
|
||||
"codeberg.org/rimgo/rimgo/static"
|
||||
"codeberg.org/rimgo/rimgo/utils"
|
||||
)
|
||||
|
||||
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 !utils.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 {
|
||||
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
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -1,54 +1,58 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"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)
|
||||
path := r.URL.Path
|
||||
|
||||
if utils.Config.ForceWebp &&
|
||||
!strings.HasSuffix(c.Path(), ".webp") &&
|
||||
c.Get("Sec-Fetch-Dest") == "image" &&
|
||||
c.Query("no_webp") == "" &&
|
||||
c.Accepts("image/webp") == "image/webp" &&
|
||||
!strings.HasPrefix(c.Path(), "/stack") {
|
||||
!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(c.Path(), "/")
|
||||
c.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename*": filename}))
|
||||
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)
|
||||
@@ -58,8 +62,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)
|
||||
@@ -68,17 +73,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 RenderError(w, r, 404)
|
||||
} else if res.StatusCode == 429 {
|
||||
return utils.RenderError(c, 429)
|
||||
return 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
|
||||
}
|
||||
|
||||
@@ -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 RenderError(w, r, 429)
|
||||
}
|
||||
if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") {
|
||||
return utils.RenderError(c, 404)
|
||||
return 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,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
65
pages/rss.go
65
pages/rss.go
@@ -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 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
|
||||
}
|
||||
|
||||
@@ -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,7 +37,7 @@ func HandleSearch(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("search", fiber.Map{
|
||||
return render.Render(w, "search", map[string]any{
|
||||
"query": query,
|
||||
"results": results,
|
||||
"page": pageNumber,
|
||||
|
||||
29
pages/tag.go
29
pages/tag.go
@@ -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 RenderError(w, r, 429)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.Display == "" {
|
||||
return utils.RenderError(c, 404)
|
||||
return 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,
|
||||
|
||||
@@ -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,7 +42,7 @@ func HandleTrending(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("trending", fiber.Map{
|
||||
return render.Render(w, "trending", map[string]any{
|
||||
"results": results,
|
||||
"section": section,
|
||||
"sort": sort,
|
||||
|
||||
@@ -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 RenderError(w, r, 429)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.Username == "" {
|
||||
return utils.RenderError(c, 404)
|
||||
return 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 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 RenderError(w, r, 429)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.Username == "" {
|
||||
return utils.RenderError(c, 404)
|
||||
return 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 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 RenderError(w, r, 429)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.Username == "" {
|
||||
return utils.RenderError(c, 404)
|
||||
return 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 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,
|
||||
|
||||
44
render/helpers.go
Normal file
44
render/helpers.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/rimgo/rimgo/utils"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/mailgun/raymond/v2"
|
||||
)
|
||||
|
||||
func (r *renderer) registerHelpers() {
|
||||
funcmap := map[string]any{
|
||||
"noteq": noteq,
|
||||
"ifNonZeroTime": ifNonZeroTime,
|
||||
"relTime": relTime,
|
||||
"rewriteUrl": rewriteUrl,
|
||||
"sanitizeDescription": sanitizeDescription,
|
||||
"sanitizeComment": sanitizeComment,
|
||||
}
|
||||
raymond.RegisterHelpers(funcmap)
|
||||
}
|
||||
|
||||
func noteq(a, b any, options *raymond.Options) any {
|
||||
if raymond.Str(a) != raymond.Str(b) {
|
||||
return options.Fn()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func ifNonZeroTime(v any, options *raymond.Options) any {
|
||||
if v.(time.Time).IsZero() {
|
||||
return ""
|
||||
}
|
||||
return options.Fn()
|
||||
}
|
||||
func relTime(date time.Time) string {
|
||||
return humanize.Time(date)
|
||||
}
|
||||
func rewriteUrl(link string) string {
|
||||
r, err := utils.RewriteUrl(link)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
76
render/render.go
Normal file
76
render/render.go
Normal 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
|
||||
}
|
||||
53
render/sanitize.go
Normal file
53
render/sanitize.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"gitlab.com/golang-commonmark/linkify"
|
||||
)
|
||||
|
||||
var imgurRe = regexp.MustCompile(`https?://imgur\.com/(gallery|a)?/(.*)`)
|
||||
var imgurRe2 = regexp.MustCompile(`https?://imgur\.com/(.*)`)
|
||||
var imgRe = regexp.MustCompile(`https?://i\.imgur\.com/(.*)\.(png|gif|jpe?g|webp)`)
|
||||
var vidRe = regexp.MustCompile(`https?://i\.imgur\.com/(.*)\.(mp4|webm)`)
|
||||
var vidFormatRe = regexp.MustCompile(`\.(mp4|webm)`)
|
||||
var iImgurRe = regexp.MustCompile(`https?://i\.imgur\.com`)
|
||||
|
||||
func sanitizeDescription(src string) string {
|
||||
src = strings.ReplaceAll(src, "\n", "<br>")
|
||||
return bluemonday.UGCPolicy().Sanitize(src)
|
||||
}
|
||||
func sanitizeComment(src string) string {
|
||||
src = strings.ReplaceAll(src, "\n", "<br>")
|
||||
|
||||
for _, match := range imgRe.FindAllString(src, -1) {
|
||||
img := iImgurRe.ReplaceAllString(match, "")
|
||||
img = `<img src="` + img + `" class="comment__media" loading="lazy"/>`
|
||||
src = strings.Replace(src, match, img, 1)
|
||||
}
|
||||
for _, match := range vidRe.FindAllString(src, -1) {
|
||||
vid := iImgurRe.ReplaceAllString(match, "")
|
||||
vid = `<video class="comment__media" controls loop preload="none" poster="` + vidFormatRe.ReplaceAllString(vid, ".webp") + `"><source type="` + strings.Split(vid, ".")[1] + `" src="` + vid + `" /></video>`
|
||||
src = strings.Replace(src, match, vid, 1)
|
||||
}
|
||||
for _, l := range linkify.Links(src) {
|
||||
origLink := (src)[l.Start:l.End]
|
||||
link := `<a href="` + origLink + `">` + origLink + `</a>`
|
||||
src = strings.Replace(src, origLink, link, 1)
|
||||
}
|
||||
src = imgurRe.ReplaceAllString(src, "/$1/$2")
|
||||
src = imgurRe2.ReplaceAllString(src, "/$1")
|
||||
|
||||
p := bluemonday.UGCPolicy()
|
||||
p.AllowImages()
|
||||
p.AllowElements("video", "source")
|
||||
p.AllowAttrs("src", "tvpe").OnElements("source")
|
||||
p.AllowAttrs("controls", "loop", "preload", "poster").OnElements("video")
|
||||
p.AllowAttrs("class", "loading").OnElements("img", "video")
|
||||
p.RequireNoReferrerOnLinks(true)
|
||||
p.RequireNoFollowOnLinks(true)
|
||||
p.RequireCrossOriginAnonymous(true)
|
||||
return p.Sanitize(src)
|
||||
}
|
||||
22
utils/accepts.go
Normal file
22
utils/accepts.go
Normal 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
|
||||
}
|
||||
@@ -11,7 +11,6 @@ type config struct {
|
||||
ImgurId string
|
||||
ProtocolDetection bool
|
||||
Secure bool
|
||||
FiberPrefork bool
|
||||
ForceWebp bool
|
||||
ImageCache bool
|
||||
CleanupInterval time.Duration
|
||||
@@ -21,51 +20,37 @@ type config struct {
|
||||
|
||||
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,
|
||||
ProtocolDetection: os.Getenv("PROTOCOL_DETECTION") == "true" || os.Getenv("PROTOCOL_DETECTION") == "1",
|
||||
Secure: os.Getenv("SECURE") == "true" || os.Getenv("SECURE") == "1",
|
||||
FiberPrefork: os.Getenv("FIBER_PREFORK") == "true" || os.Getenv("FIBER_PREFORK") == "1",
|
||||
ForceWebp: os.Getenv("FORCE_WEBP") == "true" || os.Getenv("FORCE_WEBP") == "1",
|
||||
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"),
|
||||
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" || os.Getenv("PRIVACY_CLOUDFLARE") == "1",
|
||||
"not_collected": os.Getenv("PRIVACY_NOT_COLLECTED") == "true" || os.Getenv("PRIVACY_NOT_COLLECTED") == "1",
|
||||
"ip": os.Getenv("PRIVACY_IP") == "true" || os.Getenv("PRIVACY_IP") == "1",
|
||||
"url": os.Getenv("PRIVACY_URL") == "true" || os.Getenv("PRIVACY_URL") == "1",
|
||||
"device": os.Getenv("PRIVACY_DEVICE") == "true" || os.Getenv("PRIVACY_DEVICE") == "1",
|
||||
"diagnostics": os.Getenv("PRIVACY_DIAGNOSTICS") == "true" || os.Getenv("PRIVACY_DIAGNOSTICS") == "1",
|
||||
"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"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
package utils
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
import "net/http"
|
||||
|
||||
func GetInstanceProtocol(c *fiber.Ctx) string {
|
||||
func GetInstanceProtocol(r *http.Request) string {
|
||||
proto := "https"
|
||||
if !Config.Secure {
|
||||
proto = "http"
|
||||
}
|
||||
if Config.ProtocolDetection {
|
||||
proto = c.Get("X-Forwarded-Proto", proto)
|
||||
xproto := r.Header.Get("X-Forwarded-Proto")
|
||||
if xproto != "" {
|
||||
proto = xproto
|
||||
}
|
||||
}
|
||||
return proto
|
||||
}
|
||||
|
||||
func GetInstanceUrl(c *fiber.Ctx) string {
|
||||
return GetInstanceProtocol(c) + "://" + c.Hostname()
|
||||
func GetInstanceUrl(r *http.Request) string {
|
||||
return GetInstanceProtocol(r) + "://" + r.Host
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -9,6 +10,42 @@ import (
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func GetJSONNew(url string) (json.RawMessage, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return json.RawMessage{}, err
|
||||
}
|
||||
|
||||
SetReqHeaders(req)
|
||||
|
||||
client := http.Client{}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return json.RawMessage{}, err
|
||||
}
|
||||
rateLimitRemaining := res.Header.Get("X-RateLimit-UserRemaining")
|
||||
if rateLimitRemaining != "" {
|
||||
ratelimit, _ := strconv.Atoi(rateLimitRemaining)
|
||||
if ratelimit <= 0 {
|
||||
return json.RawMessage{}, fmt.Errorf("ratelimited by imgur")
|
||||
}
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return json.RawMessage{}, err
|
||||
}
|
||||
|
||||
switch res.StatusCode {
|
||||
case 200:
|
||||
return body, nil
|
||||
case 429:
|
||||
return json.RawMessage{}, fmt.Errorf("ratelimited by imgur")
|
||||
default:
|
||||
return json.RawMessage{}, fmt.Errorf("received status %s, expected 200 OK.\n%s", res.Status, string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func GetJSON(url string) (gjson.Result, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
@@ -35,7 +72,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:
|
||||
|
||||
25
utils/rewriteUrl.go
Normal file
25
utils/rewriteUrl.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RewriteUrl(link string) (string, error) {
|
||||
url, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := url.Path
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
switch url.Host {
|
||||
case "", "imgur.com", "www.imgur.com", "i.imgur.com":
|
||||
return path, nil
|
||||
case "i.stack.imgur.com":
|
||||
return "/stack" + path, nil
|
||||
}
|
||||
return "", fmt.Errorf("unknown host %s", url.Host)
|
||||
}
|
||||
@@ -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
16
utils/splitNameExt.go
Normal 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
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
{{#noteq this.User.Username "[deleted]"}}
|
||||
<img src="{{this.User.Avatar}}" class="rounded-full" width="24" height="24" loading="lazy">
|
||||
<a href="/user/{{this.User.Username}}">
|
||||
<p class="whitespace-nowrap text-ellipsis overflow-hidden"><b>{{this.User.Username}}</b></p>
|
||||
{{#noteq this.Account.Username "[deleted]"}}
|
||||
<img src="{{rewriteUrl(this.Account.Avatar)}}" class="rounded-full" width="24" height="24" loading="lazy">
|
||||
<a href="/user/{{this.Account.Username}}">
|
||||
<p class="whitespace-nowrap text-ellipsis overflow-hidden"><b>{{this.Account.Username}}</b></p>
|
||||
</a>
|
||||
{{/noteq}}
|
||||
{{#equal this.User.Username "[deleted]"}}
|
||||
{{#equal this.Account.Username "[deleted]"}}
|
||||
<p class="whitespace-nowrap text-ellipsis overflow-hidden"><b>[deleted]</b></p>
|
||||
{{/equal}}
|
||||
</div>
|
||||
<div>
|
||||
<p>{{{this.Comment}}}</p>
|
||||
<p>{{{sanitizeComment(this.Comment)}}}</p>
|
||||
<div class="flex gap-2">
|
||||
<span title="{{this.CreatedAt}}">{{this.RelTime}}</span>
|
||||
{{#if this.DeletedAt}}
|
||||
<span title="{{this.CreatedAt}}">{{relTime(this.CreatedAt)}}</span>
|
||||
{{#ifNonZeroTime this.DeletedAt}}
|
||||
<span class="text-md">(deleted {{this.DeletedAt}})</span>
|
||||
{{/if}}
|
||||
{{/ifNonZeroTime}}
|
||||
|
|
||||
<img class="invert icon" src="/static/icons/PhArrowFatUp.svg" alt="Likes" width="24px" height="24px"> {{this.Upvotes}}
|
||||
<img class="invert icon" src="/static/icons/PhArrowFatDown.svg" alt="Dislikes" width="24px" height="24px"> {{this.Downvotes}}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<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 h-full">
|
||||
<p class="md-container">{{{this.Comment}}}</p>
|
||||
<p class="md-container">{{{sanitizeComment(this.Comment)}}}</p>
|
||||
<div class="grow"></div>
|
||||
<div class="flex gap-2">
|
||||
<span title="{{this.CreatedAt}}">{{this.RelTime}}</span>
|
||||
{{#if this.DeletedAt}}
|
||||
<span title="{{this.CreatedAt}}">{{relTime(this.CreatedAt)}}</span>
|
||||
{{#ifNonZeroTime this.DeletedAt}}
|
||||
<span class="text-md">(deleted {{this.DeletedAt}})</span>
|
||||
{{/if}}
|
||||
{{/ifNonZeroTime}}
|
||||
|
|
||||
<img class="invert icon" src="/static/icons/PhArrowFatUp.svg" alt="Likes" width="24px" height="24px">
|
||||
{{this.Upvotes}}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
{{/equal}}
|
||||
|
||||
{{#if this.Description}}
|
||||
<p>{{{this.Description}}}</p>
|
||||
<p>{{{sanitizeDescription(this.Description)}}}</p>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user