11 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
25 changed files with 525 additions and 414 deletions

View File

@@ -36,8 +36,6 @@ func (client *Client) FetchTrending(section, sort, page string) ([]Submission, e
case "best": case "best":
q.Add("filter[window]", "all") q.Add("filter[window]", "all")
q.Add("sort", "-top") q.Add("sort", "-top")
case "random":
q.Add("sort", "random")
case "popular": case "popular":
fallthrough fallthrough
default: default:
@@ -53,8 +51,6 @@ func (client *Client) FetchTrending(section, sort, page string) ([]Submission, e
case "top": case "top":
q.Add("filter[section]", "eq:top") q.Add("filter[section]", "eq:top")
q.Add("filter[window]", "day") q.Add("filter[window]", "day")
case "random":
q.Add("filter[section]", "eq:random")
default: default:
q.Add("filter[section]", "eq:hot") q.Add("filter[section]", "eq:hot")
section = "hot" section = "hot"

17
go.mod
View File

@@ -5,8 +5,6 @@ go 1.24.0
require ( require (
github.com/PuerkitoBio/goquery v1.11.0 github.com/PuerkitoBio/goquery v1.11.0
github.com/dustin/go-humanize v1.0.1 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/gorilla/feeds v1.2.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mailgun/raymond/v2 v2.0.48 github.com/mailgun/raymond/v2 v2.0.48
@@ -17,26 +15,13 @@ require (
) )
require ( require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aymerick/douceur v0.2.0 // 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/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/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/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // 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/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect golang.org/x/text v0.32.0 // indirect

35
go.sum
View File

@@ -1,57 +1,31 @@
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= 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 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= 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 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gofiber/fiber/v2 v2.52.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/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 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 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 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= 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 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/philhofer/fwd v1.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= 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.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinylib/msgp v1.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= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 h1:1Coh5BsUBlXoEJmIEaNzVAWrtg9k7/eJzailMQr1grw= gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 h1:1Coh5BsUBlXoEJmIEaNzVAWrtg9k7/eJzailMQr1grw=
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8= gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8=
@@ -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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

224
main.go
View File

@@ -3,23 +3,37 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"time" "strings"
"codeberg.org/rimgo/rimgo/pages" "codeberg.org/rimgo/rimgo/pages"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/static" "codeberg.org/rimgo/rimgo/static"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"codeberg.org/rimgo/rimgo/views" "codeberg.org/rimgo/rimgo/views"
"github.com/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/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 {
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() { func main() {
envPath := flag.String("c", ".env", "Path to env file") envPath := flag.String("c", ".env", "Path to env file")
godotenv.Load(*envPath) godotenv.Load(*envPath)
@@ -27,120 +41,110 @@ func main() {
pages.InitializeApiClient() pages.InitializeApiClient()
views := http.FS(views.GetFiles()) views := views.GetFiles()
if os.Getenv("ENV") == "dev" { static := static.GetFiles()
views = http.Dir("./views") render.Initialize(views)
}
engine := handlebars.NewFileSystem(views, ".hbs")
engine.AddFunc("noteq", func(a interface{}, b interface{}, options *raymond.Options) interface{} { app := http.NewServeMux()
if raymond.Str(a) != raymond.Str(b) {
return options.Fn()
}
return ""
})
app := fiber.New(fiber.Config{ app.Handle("GET /static/", http.StripPrefix("/static/", http.FileServerFS(static)))
Views: engine, app.Handle("GET /robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Prefork: utils.Config.FiberPrefork, file, _ := static.Open("robots.txt")
UnescapePath: true, defer file.Close()
StreamRequestBody: true, io.Copy(w, file)
ErrorHandler: func(ctx *fiber.Ctx, err error) error { }))
code := fiber.StatusInternalServerError app.Handle("GET /favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file, _ := static.Open("favicon/favicon.ico")
if e, ok := err.(*fiber.Error); ok { defer file.Close()
code = e.Code io.Copy(w, file)
}
return utils.RenderError(ctx, code)
},
})
app.Use(recover.New(recover.Config{
EnableStackTrace: true,
StackTraceHandler: func(c *fiber.Ctx, e interface{}) {
fmt.Println(e)
},
})) }))
if os.Getenv("ENV") == "dev" { if os.Getenv("ENV") == "dev" {
app.Use("/static", filesystem.New(filesystem.Config{ app.Handle("GET /errors/429", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Root: http.Dir("./static"), utils.RenderError(w, r, 429)
})) }))
app.Get("/errors/429", func(c *fiber.Ctx) error { app.Handle("GET /errors/429/img", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return c.Render("errors/429", nil) w.Header().Set("Location", "/static/img/error-429.png")
}) w.WriteHeader(302)
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.Use(cache.New(cache.Config{ app.Handle("GET /errors/404", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expiration: 30 * time.Minute, utils.RenderError(w, r, 404)
MaxBytes: 25000000, }))
KeyGenerator: func(c *fiber.Ctx) string { app.Handle("GET /errors/404/img", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return utils.GetInstanceUrl(c) + c.OriginalURL() w.Header().Set("Location", "/static/img/error-404.png")
}, w.WriteHeader(302)
CacheControl: true, }))
StoreResponseHeaders: true, 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.Handle("GET /{$}", wrapHandler(pages.HandleFrontpage))
app.Get("/robots.txt", func(c *fiber.Ctx) error { app.Handle("GET /a/{postID}", wrapHandler(pages.HandlePost))
file, _ := static.GetFiles().ReadFile("robots.txt") app.Handle("GET /a/{postID}/embed", wrapHandler(pages.HandleEmbed))
_, err := c.Write(file) 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 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) addr := utils.Config.Addr + ":" + utils.Config.Port
app.Get("/about", pages.HandleAbout) fmt.Println("listening on " + addr)
app.Get("/privacy", pages.HandlePrivacy) err := http.ListenAndServe(addr, app)
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)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }

View File

@@ -1,19 +1,21 @@
package pages package pages
import ( import (
"net/http"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
func HandleAbout(c *fiber.Ctx) error { func HandleAbout(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=31557600") w.Header().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") 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("about", fiber.Map{ return render.Render(w, "about", map[string]any{
"proto": c.Protocol(), "proto": r.Proto,
"domain": c.Hostname(), "domain": r.Host,
"force_webp": utils.Config.ForceWebp, "force_webp": utils.Config.ForceWebp,
}) })
} }

View File

@@ -1,48 +1,49 @@
package pages package pages
import ( import (
"net/http"
"strings" "strings"
"codeberg.org/rimgo/rimgo/api" "codeberg.org/rimgo/rimgo/api"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
func HandleEmbed(c *fiber.Ctx) error { func HandleEmbed(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("Cache-Control", "public,max-age=31557600") w.Header().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") 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) post, err := api.Album{}, error(nil)
switch { switch {
case strings.HasPrefix(c.Path(), "/a"): case strings.HasPrefix(r.URL.Path, "/a"):
post, err = ApiClient.FetchAlbum(c.Params("postID")) post, err = ApiClient.FetchAlbum(r.PathValue("postID"))
case strings.HasPrefix(c.Path(), "/gallery"): case strings.HasPrefix(r.URL.Path, "/gallery"):
post, err = ApiClient.FetchPosts(c.Params("postID")) post, err = ApiClient.FetchPosts(r.PathValue("postID"))
default: default:
post, err = ApiClient.FetchMedia(c.Params("postID")) post, err = ApiClient.FetchMedia(r.PathValue("postID"))
} }
if err != nil && err.Error() == "ratelimited by imgur" { 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") { 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 err
} }
return c.Render("embed", fiber.Map{ return render.Render(w, "embed", map[string]any{
"post": post, "post": post,
}) })
} }
func HandleGifv(c *fiber.Ctx) error { func HandleGifv(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("Cache-Control", "public,max-age=31557600") w.Header().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") 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{ return render.Render(w, "gifv", map[string]any{
"id": c.Params("postID"), "id": r.PathValue("postID"),
}) })
} }

View File

@@ -1,19 +1,21 @@
package pages package pages
import ( import (
"net/http"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
var VersionInfo string var VersionInfo string
func HandleFrontpage(c *fiber.Ctx) error { func HandleFrontpage(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=31557600") w.Header().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") 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, "config": utils.Config,
"version": VersionInfo, "version": VersionInfo,
}) })

View File

@@ -1,54 +1,61 @@
package pages package pages
import ( import (
"io"
"mime" "mime"
"net/http" "net/http"
"strings" "strings"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
func HandleMedia(c *fiber.Ctx) error { func HandleMedia(w http.ResponseWriter, r *http.Request) error {
c.Set("Cache-Control", "public,max-age=31557600") w.Header().Set("Cache-Control", "public,max-age=31557600")
c.Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'") w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'")
if strings.HasPrefix(c.Path(), "/stack") { splitName := strings.SplitN(r.PathValue("component"), ".", 2)
return handleMedia(c, "https://i.stack.imgur.com/"+strings.ReplaceAll(c.Params("baseName"), "stack/", "")+"."+c.Params("extension")) 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 { } 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 { func HandleUserCover(w http.ResponseWriter, r *http.Request) error {
c.Set("Cache-Control", "public,max-age=604800") w.Header().Set("Cache-Control", "public,max-age=604800")
c.Set("Content-Security-Policy", "default-src 'none'") w.Header().Set("Content-Security-Policy", "default-src 'none'")
return handleMedia(c, "https://imgur.com/user/"+c.Params("userID")+"/cover?maxwidth=2560") return handleMedia(w, r, "https://imgur.com/user/"+r.PathValue("userID")+"/cover?maxwidth=2560")
} }
func HandleUserAvatar(c *fiber.Ctx) error { func HandleUserAvatar(w http.ResponseWriter, r *http.Request) error {
c.Set("Cache-Control", "public,max-age=604800") w.Header().Set("Cache-Control", "public,max-age=604800")
c.Set("Content-Security-Policy", "default-src 'none'") w.Header().Set("Content-Security-Policy", "default-src 'none'")
return handleMedia(c, "https://imgur.com/user/"+c.Params("userID")+"/avatar") return handleMedia(w, r, "https://imgur.com/user/"+r.PathValue("userID")+"/avatar")
} }
func handleMedia(c *fiber.Ctx, url string) error { func handleMedia(w http.ResponseWriter, r *http.Request, url string) error {
utils.SetHeaders(c) utils.SetHeaders(w)
if !utils.Config.RestrictiveCORS {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
path := r.URL.Path
if utils.Config.ForceWebp && if utils.Config.ForceWebp &&
!strings.HasSuffix(c.Path(), ".webp") && !strings.HasSuffix(path, ".webp") &&
c.Get("Sec-Fetch-Dest") == "image" && r.Header.Get("Sec-Fetch-Dest") == "image" &&
c.Query("no_webp") == "" && r.URL.Query().Get("no_webp") == "" &&
c.Accepts("image/webp") == "image/webp" && utils.Accepts(r, "image/webp") &&
!strings.HasPrefix(c.Path(), "/stack") { !strings.HasPrefix(path, "/stack") {
url = strings.ReplaceAll(url, ".png", ".webp") url = strings.ReplaceAll(url, ".png", ".webp")
url = strings.ReplaceAll(url, ".jpg", ".webp") url = strings.ReplaceAll(url, ".jpg", ".webp")
url = strings.ReplaceAll(url, ".jpeg", ".webp") url = strings.ReplaceAll(url, ".jpeg", ".webp")
filename := strings.TrimPrefix(c.Path(), "/") filename := strings.TrimPrefix(path, "/")
c.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename*": filename})) w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename*": filename}))
} }
if strings.HasPrefix(c.Path(), "/stack") && strings.Contains(c.OriginalURL(), "?") { queryStr := r.URL.Query().Encode()
url = url + "?" + strings.Split(c.OriginalURL(), "?")[1] if strings.HasPrefix(path, "/stack") && queryStr != "" {
url = url + "?" + queryStr
} }
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
@@ -58,8 +65,9 @@ func handleMedia(c *fiber.Ctx, url string) error {
utils.SetReqHeaders(req) utils.SetReqHeaders(req)
if c.Get("Range") != "" { rng := r.URL.Query().Get("Range")
req.Header.Set("Range", c.Get("Range")) if rng != "" {
req.Header.Set("Range", rng)
} }
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
@@ -68,17 +76,18 @@ func handleMedia(c *fiber.Ctx, url string) error {
} }
if res.StatusCode == 404 || strings.Contains(res.Request.URL.String(), "error/404") { 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 { } else if res.StatusCode == 429 {
return utils.RenderError(c, 429) return utils.RenderError(w, r, 429)
} }
c.Set("Accept-Ranges", "bytes") w.Header().Set("Accept-Ranges", "bytes")
c.Set("Content-Type", res.Header.Get("Content-Type")) w.Header().Set("Content-Type", res.Header.Get("Content-Type"))
c.Set("Content-Length", res.Header.Get("Content-Length")) w.Header().Set("Content-Length", res.Header.Get("Content-Length"))
if res.Header.Get("Content-Range") != "" { if res.Header.Get("Content-Range") != "" {
c.Set("Content-Range", res.Header.Get("Content-Range")) 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 ( import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"strings" "strings"
"codeberg.org/rimgo/rimgo/api" "codeberg.org/rimgo/rimgo/api"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
// Cursed function // Cursed function
@@ -33,31 +34,31 @@ func nextInTag(client *api.Client, tagname, sort, page, I string) string {
return tag.Posts[i+1].Link return tag.Posts[i+1].Link
} }
func HandlePost(c *fiber.Ctx) error { func HandlePost(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
postId := c.Params("postID") postId := r.PathValue("postID")
if strings.Contains(postId, "-") { if strings.Contains(postId, "-") {
postId = postId[len(postId)-7:] postId = postId[len(postId)-7:]
} }
post, err := api.Album{}, error(nil) post, err := api.Album{}, error(nil)
switch { switch {
case strings.HasPrefix(c.Path(), "/a"): case strings.HasPrefix(r.URL.Path, "/a"):
post, err = ApiClient.FetchAlbum(postId) post, err = ApiClient.FetchAlbum(postId)
case strings.HasPrefix(c.Path(), "/gallery"): case strings.HasPrefix(r.URL.Path, "/gallery"):
post, err = ApiClient.FetchPosts(postId) post, err = ApiClient.FetchPosts(postId)
case strings.HasPrefix(c.Path(), "/t"): case strings.HasPrefix(r.URL.Path, "/t"):
post, err = ApiClient.FetchPosts(postId) post, err = ApiClient.FetchPosts(postId)
default: default:
post, err = ApiClient.FetchMedia(postId) post, err = ApiClient.FetchMedia(postId)
} }
if err != nil && err.Error() == "ratelimited by imgur" { 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") { 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 err
@@ -65,13 +66,13 @@ func HandlePost(c *fiber.Ctx) error {
comments := []api.Comment{} comments := []api.Comment{}
if post.SharedWithCommunity { if post.SharedWithCommunity {
c.Set("Cache-Control", "public,max-age=604800") w.Header().Set("Cache-Control", "public,max-age=604800")
comments, err = ApiClient.FetchComments(postId) comments, err = ApiClient.FetchComments(postId)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
c.Set("Cache-Control", "public,max-age=31557600") w.Header().Set("Cache-Control", "public,max-age=31557600")
} }
nonce := "" nonce := ""
@@ -82,16 +83,16 @@ func HandlePost(c *fiber.Ctx) error {
nonce = fmt.Sprintf("%x", b) nonce = fmt.Sprintf("%x", b)
csp = csp + " 'nonce-" + nonce + "'" csp = csp + " 'nonce-" + nonce + "'"
} }
c.Set("Content-Security-Policy", csp) w.Header().Set("Content-Security-Policy", csp)
var next string var next string
tagParam := strings.Split(c.Query("tag"), ".") tagParam := strings.Split(r.URL.Query().Get("tag"), ".")
if len(tagParam) == 4 { if len(tagParam) == 4 {
tag, sort, page, index := tagParam[0], tagParam[1], tagParam[2], tagParam[3] tag, sort, page, index := tagParam[0], tagParam[1], tagParam[2], tagParam[3]
next = nextInTag(ApiClient, tag, sort, page, index) next = nextInTag(ApiClient, tag, sort, page, index)
} }
return c.Render("post", fiber.Map{ return render.Render(w, "post", map[string]any{
"post": post, "post": post,
"next": next, "next": next,
"comments": comments, "comments": comments,

View File

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

View File

@@ -1,49 +1,54 @@
package pages package pages
import ( import (
"mime"
"net/http"
"time" "time"
"codeberg.org/rimgo/rimgo/api" "codeberg.org/rimgo/rimgo/api"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
"github.com/gorilla/feeds" "github.com/gorilla/feeds"
) )
func HandleTagRSS(c *fiber.Ctx) error { func HandleTagRSS(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) 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" { 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 { if err != nil {
return err return err
} }
if tag.Display == "" { 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{ feed := &feeds.Feed{
Title: tag.Display + " on Imgur", 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(), Created: time.Now(),
} }
return handleFeed(c, instance, feed, tag.Posts) return handleFeed(w, r, instance, feed, tag.Posts)
} }
func HandleTrendingRSS(c *fiber.Ctx) error { func HandleTrendingRSS(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
section := c.Query("section") section := r.URL.Query().Get("section")
switch section { switch section {
case "hot", "new", "top": case "hot", "new", "top":
default: default:
section = "hot" section = "hot"
} }
sort := c.Query("sort") sort := r.URL.Query().Get("sort")
switch sort { switch sort {
case "newest", "best", "popular": case "newest", "best", "popular":
default: default:
@@ -55,7 +60,7 @@ func HandleTrendingRSS(c *fiber.Ctx) error {
return err return err
} }
instance := utils.GetInstanceUrl(c) instance := utils.GetInstanceUrl(r)
feed := &feeds.Feed{ feed := &feeds.Feed{
Title: "Trending on Imgur", Title: "Trending on Imgur",
@@ -63,24 +68,23 @@ func HandleTrendingRSS(c *fiber.Ctx) error {
Created: time.Now(), Created: time.Now(),
} }
return handleFeed(c, instance, feed, results) return handleFeed(w, r, instance, feed, results)
} }
func HandleUserRSS(c *fiber.Ctx) error { func HandleUserRSS(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
user := c.Params("userID") user := r.PathValue("userID")
submissions, err := ApiClient.FetchSubmissions(user, "newest", "1") submissions, err := ApiClient.FetchSubmissions(user, "newest", "1")
if err != nil && err.Error() == "ratelimited by imgur" { if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429) return utils.RenderError(w, r, 429)
return utils.RenderError(c, 429)
} }
if err != nil { if err != nil {
return err return err
} }
instance := utils.GetInstanceUrl(c) instance := utils.GetInstanceUrl(r)
feed := &feeds.Feed{ feed := &feeds.Feed{
Title: user + " on Imgur", Title: user + " on Imgur",
@@ -88,10 +92,10 @@ func HandleUserRSS(c *fiber.Ctx) error {
Created: time.Now(), 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{} feed.Items = []*feeds.Item{}
for _, post := range posts { 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) feed.Items = append(feed.Items, item)
} }
c.Type(c.Params("type")) w.Header().Set("Content-Type", mime.TypeByExtension("."+r.PathValue("type")))
switch c.Params("type") { switch r.PathValue("type") {
case "atom": case "atom":
body, err := feed.ToAtom() body, err := feed.ToAtom()
if err != nil { if err != nil {
return err return err
} }
return c.SendString(body) w.Write([]byte(body))
case "json": case "json":
body, err := feed.ToJSON() body, err := feed.ToJSON()
if err != nil { if err != nil {
return err return err
} }
return c.JSON(body) w.Write([]byte(body))
case "rss": case "rss":
body, err := feed.ToRss() body, err := feed.ToRss()
if err != nil { if err != nil {
return err return err
} }
return c.SendString(body) w.Write([]byte(body))
default: 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 package pages
import ( import (
"net/http"
"strconv" "strconv"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
func HandleSearch(c *fiber.Ctx) error { func HandleSearch(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=604800") w.Header().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") 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) { 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" page := r.URL.Query().Get("page")
if c.Query("page") != "" { if page == "" {
page = c.Query("page") page = "0"
} }
pageNumber, err := strconv.Atoi(c.Query("page")) pageNumber, err := strconv.Atoi(page)
if err != nil { if err != nil {
pageNumber = 0 pageNumber = 0
} }
@@ -34,7 +37,7 @@ func HandleSearch(c *fiber.Ctx) error {
return err return err
} }
return c.Render("search", fiber.Map{ return render.Render(w, "search", map[string]any{
"query": query, "query": query,
"results": results, "results": results,
"page": pageNumber, "page": pageNumber,

View File

@@ -1,40 +1,41 @@
package pages package pages
import ( import (
"net/http"
"strconv" "strconv"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
func HandleTag(c *fiber.Ctx) error { func HandleTag(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=604800") w.Header().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") 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" page := r.URL.Query().Get("page")
if c.Query("page") != "" { if page == "" {
page = c.Query("page") page = "1"
} }
pageNumber, err := strconv.Atoi(c.Query("page")) pageNumber, err := strconv.Atoi(page)
if err != nil { if err != nil {
pageNumber = 0 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" { if err != nil && err.Error() == "ratelimited by imgur" {
return utils.RenderError(c, 429) return utils.RenderError(w, r, 429)
} }
if err != nil { if err != nil {
return err return err
} }
if tag.Display == "" { 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, "tag": tag,
"page": page, "page": page,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,

View File

@@ -1,35 +1,36 @@
package pages package pages
import ( import (
"net/http"
"strconv" "strconv"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2"
) )
func HandleTrending(c *fiber.Ctx) error { func HandleTrending(w http.ResponseWriter, r *http.Request) error {
utils.SetHeaders(c) utils.SetHeaders(w)
c.Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
c.Set("Cache-Control", "public,max-age=604800") w.Header().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") 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" page := r.URL.Query().Get("page")
if c.Query("page") != "" { if page == "" {
page = c.Query("page") page = "1"
} }
pageNumber, err := strconv.Atoi(c.Query("page")) pageNumber, err := strconv.Atoi(page)
if err != nil { if err != nil {
pageNumber = 1 pageNumber = 1
} }
section := c.Query("section") section := r.URL.Query().Get("section")
switch section { switch section {
case "hot", "new", "top", "random": case "hot", "new", "top":
default: default:
section = "hot" section = "hot"
} }
sort := c.Query("sort") sort := r.URL.Query().Get("sort")
switch sort { switch sort {
case "newest", "best", "popular": case "newest", "best", "popular":
default: default:
@@ -41,7 +42,7 @@ func HandleTrending(c *fiber.Ctx) error {
return err return err
} }
return c.Render("trending", fiber.Map{ return render.Render(w, "trending", map[string]any{
"results": results, "results": results,
"section": section, "section": section,
"sort": sort, "sort": sort,

View File

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

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

@@ -11,8 +11,8 @@ type config struct {
ImgurId string ImgurId string
ProtocolDetection bool ProtocolDetection bool
Secure bool Secure bool
FiberPrefork bool
ForceWebp bool ForceWebp bool
RestrictiveCORS bool
ImageCache bool ImageCache bool
CleanupInterval time.Duration CleanupInterval time.Duration
CacheDir string CacheDir string
@@ -39,8 +39,8 @@ func LoadConfig() {
ImgurId: envString("IMGUR_CLIENT_ID", "546c25a59c58ad7"), ImgurId: envString("IMGUR_CLIENT_ID", "546c25a59c58ad7"),
ProtocolDetection: envBool("PROTOCOL_DETECTION"), ProtocolDetection: envBool("PROTOCOL_DETECTION"),
Secure: envBool("SECURE"), Secure: envBool("SECURE"),
FiberPrefork: envBool("FIBER_PREFORK"),
ForceWebp: envBool("FORCE_WEBP"), ForceWebp: envBool("FORCE_WEBP"),
RestrictiveCORS: envBool("RESTRICTIVE_CORS"),
Privacy: map[string]interface{}{ Privacy: map[string]interface{}{
"set": os.Getenv("PRIVACY_NOT_COLLECTED") != "", "set": os.Getenv("PRIVACY_NOT_COLLECTED") != "",
"policy": os.Getenv("PRIVACY_POLICY"), "policy": os.Getenv("PRIVACY_POLICY"),

View File

@@ -1,25 +1,43 @@
package utils package utils
import ( import (
"fmt"
"io"
"net/http"
"strconv" "strconv"
"strings"
"codeberg.org/rimgo/rimgo/render"
"codeberg.org/rimgo/rimgo/static" "codeberg.org/rimgo/rimgo/static"
"github.com/gofiber/fiber/v2"
) )
func RenderError(c *fiber.Ctx, code int) error { func RenderError(w http.ResponseWriter, r *http.Request, code int, str ...string) (err error) {
if !strings.Contains(c.Get("Accept"), "html") && c.Params("extension") != "" { if len(str) != 1 {
codeStr := "generic" str = []string{""}
if code != 0 { }
codeStr = strconv.Itoa(code) codeStr := "generic"
} if code == 0 {
img, _ := static.GetFiles().ReadFile("img/error-" + codeStr + ".png") code = 500
c.Set("Content-Type", "image/png") }
return c.Status(code).Send(img) 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 { } else {
return c.Status(code).Render("errors/"+strconv.Itoa(code), fiber.Map{ w.WriteHeader(code)
"path": c.Path(), 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,18 +1,21 @@
package utils 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" proto := "https"
if !Config.Secure { if !Config.Secure {
proto = "http" proto = "http"
} }
if Config.ProtocolDetection { if Config.ProtocolDetection {
proto = c.Get("X-Forwarded-Proto", proto) xproto := r.Header.Get("X-Forwarded-Proto")
if xproto != "" {
proto = xproto
}
} }
return proto return proto
} }
func GetInstanceUrl(c *fiber.Ctx) string { func GetInstanceUrl(r *http.Request) string {
return GetInstanceProtocol(c) + "://" + c.Hostname() return GetInstanceProtocol(r) + "://" + r.Host
} }

View File

@@ -2,16 +2,14 @@ package utils
import ( import (
"net/http" "net/http"
"github.com/gofiber/fiber/v2"
) )
func SetHeaders(c *fiber.Ctx) { func SetHeaders(w http.ResponseWriter) {
c.Set("Referrer-Policy", "no-referrer") w.Header().Set("Referrer-Policy", "no-referrer")
c.Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Content-Type-Options", "nosniff")
c.Set("X-Robots-Tag", "noindex, noimageindex, nofollow") w.Header().Set("X-Robots-Tag", "noindex, noimageindex, nofollow")
c.Set("Strict-Transport-Security", "max-age=31557600") w.Header().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=()") 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) { 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
}

View File

@@ -30,35 +30,20 @@
<a href="?section=hot&sort={{sort}}"><b>Hot</b></a> <a href="?section=hot&sort={{sort}}"><b>Hot</b></a>
<a href="?section=new&sort={{sort}}">New</a> <a href="?section=new&sort={{sort}}">New</a>
<a href="?section=top&sort={{sort}}">Top</a> <a href="?section=top&sort={{sort}}">Top</a>
<a href="?section=random&sort=random">Random</a>
{{/equal}} {{/equal}}
{{#equal section "new"}} {{#equal section "new"}}
<a href="?section=hot&sort={{sort}}">Hot</a> <a href="?section=hot&sort={{sort}}">Hot</a>
<a href="?section=new&sort={{sort}}"><b>New</b></a> <a href="?section=new&sort={{sort}}"><b>New</b></a>
<a href="?section=top&sort={{sort}}">Top</a> <a href="?section=top&sort={{sort}}">Top</a>
<a href="?section=random&sort=random">Random</a>
{{/equal}} {{/equal}}
{{#equal section "top"}} {{#equal section "top"}}
<a href="?section=hot&sort={{sort}}">Hot</a> <a href="?section=hot&sort={{sort}}">Hot</a>
<a href="?section=new&sort={{sort}}">New</a> <a href="?section=new&sort={{sort}}">New</a>
<a href="?section=top&sort={{sort}}"><b>Top</b></a> <a href="?section=top&sort={{sort}}"><b>Top</b></a>
<a href="?section=random&sort=random">Random</a>
{{/equal}}
{{#equal section "random"}}
<a href="?section=hot&sort={{sort}}">Hot</a>
<a href="?section=new&sort={{sort}}">New</a>
<a href="?section=top&sort={{sort}}">Top</a>
<a href="?section=random&sort=random"><b>Random</b></a>
{{/equal}} {{/equal}}
</div> </div>
<hr class="sm:hidden my-2" /> <hr class="sm:hidden my-2" />
<div class="flex flex-col sm:items-end"> <div class="flex flex-col sm:items-end">
{{#equal section "random"}}
<a href="?section=hot&sort=popular">Popular</a>
<a href="?section=hot&sort=newest">Newest</a>
<a href="?section=hot&sort=best">Best</a>
{{/equal}}
{{#noteq section "random"}}
{{#equal sort "popular"}} {{#equal sort "popular"}}
<a href="?section={{section}}&sort=popular"><b>Popular</b></a> <a href="?section={{section}}&sort=popular"><b>Popular</b></a>
<a href="?section={{section}}&sort=newest">Newest</a> <a href="?section={{section}}&sort=newest">Newest</a>
@@ -74,7 +59,6 @@
<a href="?section={{section}}&sort=newest">Newest</a> <a href="?section={{section}}&sort=newest">Newest</a>
<a href="?section={{section}}&sort=best"><b>Best</b></a> <a href="?section={{section}}&sort=best"><b>Best</b></a>
{{/equal}} {{/equal}}
{{/noteq}}
</div> </div>
</div> </div>
</header> </header>