4 Commits
v1.4.1 ... 2.0

Author SHA1 Message Date
orangix
61a312aba0 implement random gallery (#245)
closes #229

Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/245
Co-authored-by: orangix <uleo8b8g@anonaddy.me>
Co-committed-by: orangix <uleo8b8g@anonaddy.me>
2026-01-23 16:54:56 +01:00
orangix
33fa04e98d change pages/about.go to Config.ForceWebp 2026-01-19 03:04:06 +01:00
orangix
e5b87dc924 gofmt 2026-01-19 02:36:52 +01:00
orangix
8cb2524924 drop RIMGU_ environment variables 2026-01-16 22:55:45 +01:00
19 changed files with 112 additions and 108 deletions

View File

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

View File

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

View File

@@ -36,6 +36,8 @@ 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:
@@ -51,6 +53,8 @@ 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"

View File

@@ -1,13 +1,10 @@
package pages package pages
import ( import (
"os"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func HandleAbout(c *fiber.Ctx) error { func HandleAbout(c *fiber.Ctx) error {
utils.SetHeaders(c) utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY") c.Set("X-Frame-Options", "DENY")
@@ -15,8 +12,8 @@ func HandleAbout(c *fiber.Ctx) error {
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") 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{ return c.Render("about", fiber.Map{
"proto": c.Protocol(), "proto": c.Protocol(),
"domain": c.Hostname(), "domain": c.Hostname(),
"force_webp": os.Getenv("FORCE_WEBP"), "force_webp": utils.Config.ForceWebp,
}) })
} }

View File

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

View File

@@ -28,7 +28,7 @@ func HandleEmbed(c *fiber.Ctx) error {
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(c, 404)
} }
if err != nil { if err != nil {
return err return err
} }
@@ -45,4 +45,4 @@ func HandleGifv(c *fiber.Ctx) error {
return c.Render("gifv", fiber.Map{ return c.Render("gifv", fiber.Map{
"id": c.Params("postID"), "id": c.Params("postID"),
}) })
} }

View File

@@ -17,4 +17,4 @@ func HandleFrontpage(c *fiber.Ctx) error {
"config": utils.Config, "config": utils.Config,
"version": VersionInfo, "version": VersionInfo,
}) })
} }

View File

@@ -35,11 +35,11 @@ func handleMedia(c *fiber.Ctx, url string) error {
utils.SetHeaders(c) utils.SetHeaders(c)
if utils.Config.ForceWebp && if utils.Config.ForceWebp &&
!strings.HasSuffix(c.Path(), ".webp") && !strings.HasSuffix(c.Path(), ".webp") &&
c.Get("Sec-Fetch-Dest") == "image" && c.Get("Sec-Fetch-Dest") == "image" &&
c.Query("no_webp") == "" && c.Query("no_webp") == "" &&
c.Accepts("image/webp") == "image/webp" && c.Accepts("image/webp") == "image/webp" &&
!strings.HasPrefix(c.Path(), "/stack") { !strings.HasPrefix(c.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")

View File

@@ -35,10 +35,10 @@ func HandleSearch(c *fiber.Ctx) error {
} }
return c.Render("search", fiber.Map{ return c.Render("search", fiber.Map{
"query": query, "query": query,
"results": results, "results": results,
"page": pageNumber, "page": pageNumber,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,
"prevPage": pageNumber - 1, "prevPage": pageNumber - 1,
}) })
} }

View File

@@ -25,7 +25,7 @@ func HandleTrending(c *fiber.Ctx) error {
section := c.Query("section") section := c.Query("section")
switch section { switch section {
case "hot", "new", "top": case "hot", "new", "top", "random":
default: default:
section = "hot" section = "hot"
} }
@@ -42,11 +42,11 @@ func HandleTrending(c *fiber.Ctx) error {
} }
return c.Render("trending", fiber.Map{ return c.Render("trending", fiber.Map{
"results": results, "results": results,
"section": section, "section": section,
"sort": sort, "sort": sort,
"page": pageNumber, "page": pageNumber,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,
"prevPage": pageNumber - 1, "prevPage": pageNumber - 1,
}) })
} }

View File

@@ -7,4 +7,4 @@ var files embed.FS
func GetFiles() embed.FS { func GetFiles() embed.FS {
return files return files
} }

View File

@@ -21,51 +21,38 @@ type config struct {
var Config config 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() { 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{ Config = config{
Port: port, Port: envString("PORT", "3000"),
Addr: addr, Addr: envString("ADDR", "0.0.0.0"),
ImgurId: imgurId, ImgurId: envString("IMGUR_CLIENT_ID", "546c25a59c58ad7"),
ProtocolDetection: os.Getenv("PROTOCOL_DETECTION") == "true" || os.Getenv("PROTOCOL_DETECTION") == "1", ProtocolDetection: envBool("PROTOCOL_DETECTION"),
Secure: os.Getenv("SECURE") == "true" || os.Getenv("SECURE") == "1", Secure: envBool("SECURE"),
FiberPrefork: os.Getenv("FIBER_PREFORK") == "true" || os.Getenv("FIBER_PREFORK") == "1", FiberPrefork: envBool("FIBER_PREFORK"),
ForceWebp: os.Getenv("FORCE_WEBP") == "true" || os.Getenv("FORCE_WEBP") == "1", ForceWebp: envBool("FORCE_WEBP"),
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"),
"message": os.Getenv("PRIVACY_MESSAGE"), "message": os.Getenv("PRIVACY_MESSAGE"),
"country": os.Getenv("PRIVACY_COUNTRY"), "country": os.Getenv("PRIVACY_COUNTRY"),
"provider": os.Getenv("PRIVACY_PROVIDER"), "provider": os.Getenv("PRIVACY_PROVIDER"),
"cloudflare": os.Getenv("PRIVACY_CLOUDFLARE") == "true" || os.Getenv("PRIVACY_CLOUDFLARE") == "1", "cloudflare": envBool("PRIVACY_CLOUDFLARE"),
"not_collected": os.Getenv("PRIVACY_NOT_COLLECTED") == "true" || os.Getenv("PRIVACY_NOT_COLLECTED") == "1", "not_collected": envBool("PRIVACY_NOT_COLLECTED"),
"ip": os.Getenv("PRIVACY_IP") == "true" || os.Getenv("PRIVACY_IP") == "1", "ip": envBool("PRIVACY_IP"),
"url": os.Getenv("PRIVACY_URL") == "true" || os.Getenv("PRIVACY_URL") == "1", "url": envBool("PRIVACY_URL"),
"device": os.Getenv("PRIVACY_DEVICE") == "true" || os.Getenv("PRIVACY_DEVICE") == "1", "device": envBool("PRIVACY_DEVICE"),
"diagnostics": os.Getenv("PRIVACY_DIAGNOSTICS") == "true" || os.Getenv("PRIVACY_DIAGNOSTICS") == "1", "diagnostics": envBool("PRIVACY_DIAGNOSTICS"),
}, },
} }
} }

View File

@@ -18,8 +18,8 @@ func RenderError(c *fiber.Ctx, code int) error {
c.Set("Content-Type", "image/png") c.Set("Content-Type", "image/png")
return c.Status(code).Send(img) return c.Status(code).Send(img)
} else { } else {
return c.Status(code).Render("errors/" + strconv.Itoa(code), fiber.Map{ return c.Status(code).Render("errors/"+strconv.Itoa(code), fiber.Map{
"path": c.Path(), "path": c.Path(),
}) })
} }
} }

View File

@@ -9,4 +9,4 @@ func FormatDate(date string) (string, error) {
} }
return time.Format("Jan 2, 2006 3:04 PM"), nil return time.Format("Jan 2, 2006 3:04 PM"), nil
} }

View File

@@ -2,4 +2,4 @@ package utils
import "regexp" import "regexp"
var ImgurRe = regexp.MustCompile(`https?://(i\.)?imgur\.com`) var ImgurRe = regexp.MustCompile(`https?://(i\.)?imgur\.com`)

View File

@@ -35,7 +35,7 @@ func GetJSON(url string) (gjson.Result, error) {
return gjson.Result{}, err return gjson.Result{}, err
} }
switch (res.StatusCode) { switch res.StatusCode {
case 200: case 200:
return gjson.Parse(string(body)), nil return gjson.Parse(string(body)), nil
case 429: case 429:
@@ -43,4 +43,4 @@ func GetJSON(url string) (gjson.Result, error) {
default: default:
return gjson.Result{}, fmt.Errorf("received status %s, expected 200 OK.\n%s", res.Status, string(body)) return gjson.Result{}, fmt.Errorf("received status %s, expected 200 OK.\n%s", res.Status, string(body))
} }
} }

View File

@@ -25,4 +25,4 @@ func SetReqHeaders(req *http.Request) {
req.Header.Set("Sec-Fetch-Mode", "cors") req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "same-site") req.Header.Set("Sec-Fetch-Site", "same-site")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0") req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0")
} }

View File

@@ -7,4 +7,4 @@ var files embed.FS
func GetFiles() embed.FS { func GetFiles() embed.FS {
return files return files
} }

View File

@@ -30,20 +30,35 @@
<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>
@@ -59,6 +74,7 @@
<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>