added user comments

This commit is contained in:
orangix 2023-08-15 17:38:44 +02:00
parent 493a17385a
commit 3faef9aeb9
No known key found for this signature in database
GPG Key ID: C31D4A86601C8416
6 changed files with 232 additions and 45 deletions

View File

@ -9,21 +9,21 @@ import (
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
"github.com/patrickmn/go-cache"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"gitlab.com/golang-commonmark/linkify" "gitlab.com/golang-commonmark/linkify"
) )
type Comment struct { type Comment struct {
Comments []Comment Comments []Comment
User User User User
Post Submission
Id string Id string
Comment string Comment string
Upvotes int64 Upvotes int64
Downvotes int64 Downvotes int64
Platform string Platform string
CreatedAt string CreatedAt string
RelTime string RelTime string
UpdatedAt string UpdatedAt string
DeletedAt string DeletedAt string
} }
@ -55,7 +55,6 @@ func (client *Client) FetchComments(galleryID string) ([]Comment, error) {
) )
wg.Wait() wg.Wait()
client.Cache.Set(galleryID + "-comments", comments, cache.DefaultExpiration)
return comments, nil return comments, nil
} }
@ -130,13 +129,14 @@ func parseComment(data gjson.Result) Comment {
Username: data.Get("account.username").String(), Username: data.Get("account.username").String(),
Avatar: userAvatar, Avatar: userAvatar,
}, },
Post: parseSubmission(data.Get("post")),
Id: data.Get("id").String(), Id: data.Get("id").String(),
Comment: comment, Comment: comment,
Upvotes: data.Get("upvote_count").Int(), Upvotes: data.Get("upvote_count").Int(),
Downvotes: data.Get("downvote_count").Int(), Downvotes: data.Get("downvote_count").Int(),
Platform: data.Get("platform").String(), Platform: data.Get("platform").String(),
CreatedAt: createdAt, CreatedAt: createdAt,
RelTime: humanize.Time(createdTime), RelTime: humanize.Time(createdTime),
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
DeletedAt: deletedAt, DeletedAt: deletedAt,
} }

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/patrickmn/go-cache"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
@ -64,7 +65,7 @@ func (client *Client) FetchUser(username string) (User, error) {
CreatedAt: createdTime.Format("January 2, 2006"), CreatedAt: createdTime.Format("January 2, 2006"),
} }
client.Cache.Set(username + "-user", user, 1*time.Hour) client.Cache.Set(username+"-user", user, 1*time.Hour)
return user, nil return user, nil
} }
@ -89,41 +90,7 @@ func (client *Client) FetchSubmissions(username string, sort string, page string
go func() { go func() {
defer wg.Done() defer wg.Done()
coverData := value.Get("images.#(id==\"" + value.Get("cover").String() + "\")") submissions = append(submissions, parseSubmission(value))
cover := Media{
Id: value.Get("id").String(),
Description: value.Get("description").String(),
Type: strings.Split(value.Get("type").String(), "/")[0],
Url: strings.ReplaceAll(value.Get("link").String(), "https://i.imgur.com", ""),
}
if coverData.Exists() {
cover = Media{
Id: coverData.Get("id").String(),
Description: coverData.Get("description").String(),
Type: strings.Split(coverData.Get("type").String(), "/")[0],
Url: strings.ReplaceAll(coverData.Get("link").String(), "https://i.imgur.com", ""),
}
}
id := value.Get("id").String()
link := "/a/" + id
if value.Get("in_gallery").Bool() {
link = "/gallery/" + id
}
submissions = append(submissions, Submission{
Id: id,
Link: link,
Title: value.Get("title").String(),
Cover: cover,
Points: value.Get("points").Int(),
Upvotes: value.Get("ups").Int(),
Downvotes: value.Get("downs").Int(),
Comments: value.Get("comment_count").Int(),
Views: value.Get("views").Int(),
IsAlbum: value.Get("is_album").Bool(),
})
}() }()
return true return true
@ -131,6 +98,91 @@ func (client *Client) FetchSubmissions(username string, sort string, page string
) )
wg.Wait() wg.Wait()
client.Cache.Set(username + "-submissions", submissions, 15*time.Minute) client.Cache.Set(username+"-submissions", submissions, 15*time.Minute)
return submissions, nil return submissions, nil
} }
func (client *Client) FetchUserComments(username string, page string) ([]Comment, error) {
req, err := http.NewRequest("GET", "https://api.imgur.com/comment/v1/comments", nil)
if err != nil {
return []Comment{}, err
}
utils.SetReqHeaders(req)
q := req.URL.Query()
q.Add("client_id", client.ClientID)
q.Add("filter[account]", "eq:wouldaponybyanyothernamebeasoffencive")
q.Add("include", "account,post")
q.Add("sort", "new")
req.URL.RawQuery = q.Encode()
res, err := http.DefaultClient.Do(req)
if err != nil {
return []Comment{}, err
}
body, err := io.ReadAll(res.Body)
if err != nil {
return []Comment{}, err
}
data := gjson.Parse(string(body))
wg := sync.WaitGroup{}
comments := make([]Comment, 0)
data.Get("data").ForEach(
func(key, value gjson.Result) bool {
wg.Add(1)
go func() {
defer wg.Done()
comments = append(comments, parseComment(value))
}()
return true
},
)
wg.Wait()
client.Cache.Set(username+"-usercomments-"+page, comments, cache.DefaultExpiration)
return comments, nil
}
func parseSubmission(value gjson.Result) Submission {
coverData := value.Get("images.#(id==\"" + value.Get("cover").String() + "\")")
cover := Media{
Id: value.Get("id").String(),
Description: value.Get("description").String(),
Type: strings.Split(value.Get("type").String(), "/")[0],
Url: strings.ReplaceAll(value.Get("link").String(), "https://i.imgur.com", ""),
}
if coverData.Exists() {
cover = Media{
Id: coverData.Get("id").String(),
Description: coverData.Get("description").String(),
Type: strings.Split(coverData.Get("type").String(), "/")[0],
Url: strings.ReplaceAll(coverData.Get("link").String(), "https://i.imgur.com", ""),
}
}
id := value.Get("id").String()
link := "/a/" + id
if value.Get("in_gallery").Bool() {
link = "/gallery/" + id
}
return Submission{
Id: id,
Link: link,
Title: value.Get("title").String(),
Cover: cover,
Points: value.Get("points").Int(),
Upvotes: value.Get("ups").Int(),
Downvotes: value.Get("downs").Int(),
Comments: value.Get("comment_count").Int(),
Views: value.Get("views").Int(),
IsAlbum: value.Get("is_album").Bool(),
}
}

View File

@ -121,8 +121,9 @@ func main() {
app.Get("/a/:postID/embed", pages.HandleEmbed) app.Get("/a/:postID/embed", pages.HandleEmbed)
app.Get("/t/:tag", pages.HandleTag) app.Get("/t/:tag", pages.HandleTag)
app.Get("/t/:tag/:postID", pages.HandlePost) app.Get("/t/:tag/:postID", pages.HandlePost)
app.Get("/user/:userID", pages.HandleUser)
app.Get("/r/:sub/:postID", pages.HandlePost) app.Get("/r/:sub/:postID", pages.HandlePost)
app.Get("/user/:userID", pages.HandleUser)
app.Get("/user/:userID/comments", pages.HandleUserComments)
app.Get("/user/:userID/cover", pages.HandleUserCover) app.Get("/user/:userID/cover", pages.HandleUserCover)
app.Get("/user/:userID/avatar", pages.HandleUserAvatar) app.Get("/user/:userID/avatar", pages.HandleUserAvatar)
app.Get("/gallery/:postID", pages.HandlePost) app.Get("/gallery/:postID", pages.HandlePost)

View File

@ -51,7 +51,56 @@ func HandleUser(c *fiber.Ctx) error {
"user": user, "user": user,
"submissions": submissions, "submissions": submissions,
"page": page, "page": page,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,
"prevPage": pageNumber - 1, "prevPage": pageNumber - 1,
})
}
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")
page := "0"
if c.Query("page") != "" {
page = c.Query("page")
}
pageNumber, err := strconv.Atoi(c.Query("page"))
if err != nil {
pageNumber = 0
}
user, err := ApiClient.FetchUser(c.Params("userID"))
if err != nil && err.Error() == "ratelimited by imgur" {
return c.Status(429).Render("errors/429", fiber.Map{
"path": c.Path(),
})
}
if err != nil {
return err
}
if user.Username == "" {
return c.Status(404).Render("errors/404", nil)
}
comments, err := ApiClient.FetchUserComments(c.Params("userID"), page)
if err != nil && err.Error() == "ratelimited by imgur" {
c.Status(429)
return c.Render("errors/429", fiber.Map{
"path": c.Path(),
})
}
if err != nil {
return err
}
return c.Render("userComments", fiber.Map{
"user": user,
"comments": comments,
"page": page,
"nextPage": pageNumber + 1,
"prevPage": pageNumber - 1,
}) })
} }

View File

@ -0,0 +1,38 @@
<!-- TODO: remove post titles and instead use cover images -->
<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">
<p class="whitespace-nowrap text-ellipsis overflow-hidden"></p><a href="/user/{{this.User.Username}}">
<b class="underline">{{this.User.Username}}</b></span>
on <a class="underline" href={{Post.Link}}>post title here {{Post.Title}}</a></p>
</a>
{{/noteq}}
{{#equal this.User.Username "[deleted]"}}
<p class="whitespace-nowrap text-ellipsis overflow-hidden"><b>[deleted]</b>
on <a class="underline" href={{Post.Link}}>post title here {{Post.Title}}</a></p>
{{/equal}}
</div>
<div>
<p>{{{this.Comment}}}</p>
<div class="flex gap-2">
<span title="{{this.CreatedAt}}">{{this.RelTime}}</span>
{{#if this.DeletedAt}}
<span class="text-md">(deleted {{this.DeletedAt}})</span>
{{/if}}
|
<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}}
</div>
</div>
{{#if this.Comments}}
<div class="ml-4 p-2 border-solid border-l-2 border-slate-400">
{{#each this.Comments}}
{{> partials/comment }}
{{/each}}
</div>
{{/if}}
</div>

47
views/userComments.hbs Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{user.Username}} - rimgo</title>
{{> partials/head }}
</head>
<body class="font-sans text-lg bg-slate-800 text-white">
{{> partials/nav }}
<section class="my-4 w-full flex flex-col items-center">
{{> partials/searchBar }}
</section>
<header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{user.Cover}}');">
<div class="flex items-center gap-2">
<img class="rounded-full" src="{{user.Avatar}}" width="72" height="72">
<div>
<h2 class="font-bold text-2xl">{{user.Username}}</h2>
<p>{{user.Points}} pts · {{user.CreatedAt}}</p>
</div>
</div>
<p class="mt-2">{{user.Bio}}</p>
</header>
<main>
<div class="comments flex flex-col gap-2">
{{#each comments}}
{{> partials/contextComment }}
{{/each}}
</div>
<div class="mt-4 font-bold">
{{#equal page "0" }}
{{else}}
<a href="{{channel.RelUrl}}?page={{prevPage}}">Previous page</a>
{{/equal}}
<a href="{{channel.RelUrl}}?page={{nextPage}}">Next page</a>
</div>
</main>
{{> partials/footer }}
</body>
</html>