package api import ( "encoding/json" "errors" "fmt" "regexp" "strings" "sync" "time" "codeberg.org/rimgo/rimgo/utils" "github.com/microcosm-cc/bluemonday" "github.com/patrickmn/go-cache" "github.com/tidwall/gjson" "gitlab.com/golang-commonmark/linkify" ) type Comment struct { ID int `json:"id"` ParentID int `json:"parent_id"` Comment string `json:"comment"` PostID string `json:"post_id"` UpvoteCount int `json:"upvote_count"` DownvoteCount int `json:"downvote_count"` PointCount int `json:"point_count"` Vote struct{} `json:"vote"` // ??? PlatformID int `json:"platform_id"` Platform string `json:"platform"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt time.Time `json:"deleted_at"` Next struct{} `json:"next"` // ??? Comments commentArray `json:"comments"` AccountID int `json:"account_id"` Account _ApiUser `json:"account"` Post commentPost `json:"post"` } type _ApiUser struct { ID int `json:"id"` Username string `json:"username"` Avatar string `json:"avatar"` } func (client *Client) FetchComments(galleryID string) ([]Comment, error) { cacheData, found := client.Cache.Get(galleryID + "-comments") if found { return cacheData.(commentArray), nil } data, err := utils.GetJSONNew("https://api.imgur.com/comment/v1/comments?client_id=" + client.ClientID + "&filter[post]=eq:" + galleryID + "&include=account,adconfig&per_page=30&sort=best") if err != nil { return []Comment{}, nil } var parsed commentApiResponse err = json.Unmarshal(data, &parsed) if err != nil { return []Comment{}, err } client.Cache.Set(galleryID+"-comments", parsed.Data, cache.DefaultExpiration) return parsed.Data, nil } var imgurRe = regexp.MustCompile(`https?://imgur\.com/(gallery|a)?/(.*)`) var imgurRe2 = regexp.MustCompile(`https?://imgur\.com/(.*)`) var imgRe = regexp.MustCompile(`https?://i\.imgur\.com/(.*)\.(png|gif|jpe?g|webp)`) var vidRe = regexp.MustCompile(`https?://i\.imgur\.com/(.*)\.(mp4|webm)`) var vidFormatRe = regexp.MustCompile(`\.(mp4|webm)`) var iImgurRe = regexp.MustCompile(`https?://i\.imgur\.com`) func parseComment(data json.RawMessage, out *Comment) { err := json.Unmarshal(data, &out) if err != nil { panic(err) } comment := &out.Comment *comment = strings.ReplaceAll(*comment, "\n", "
") for _, match := range imgRe.FindAllString(*comment, -1) { img := iImgurRe.ReplaceAllString(match, "") img = `` *comment = strings.Replace(*comment, match, img, 1) } for _, match := range vidRe.FindAllString(*comment, -1) { vid := iImgurRe.ReplaceAllString(match, "") vid = `` *comment = strings.Replace(*comment, match, vid, 1) } for _, l := range linkify.Links(*comment) { origLink := (*comment)[l.Start:l.End] link := `` + origLink + `` *comment = strings.Replace(*comment, origLink, link, 1) } *comment = imgurRe.ReplaceAllString(*comment, "/$1/$2") *comment = imgurRe2.ReplaceAllString(*comment, "/$1") p := bluemonday.UGCPolicy() p.AllowImages() p.AllowElements("video", "source") p.AllowAttrs("src", "tvpe").OnElements("source") p.AllowAttrs("controls", "loop", "preload", "poster").OnElements("video") p.AllowAttrs("class", "loading").OnElements("img", "video") p.RequireNoReferrerOnLinks(true) p.RequireNoFollowOnLinks(true) p.RequireCrossOriginAnonymous(true) *comment = p.Sanitize(*comment) } type commentArray []Comment func (arr *commentArray) UnmarshalJSON(data []byte) error { var rawArr []json.RawMessage err := json.Unmarshal(data, &rawArr) if err != nil { return err } *arr = make(commentArray, len(rawArr)) panics := make([]error, 0, len(rawArr)) wg := sync.WaitGroup{} var handlePanic = func() { if v := recover(); v != nil { v, ok := v.(error) if !ok { v = fmt.Errorf("%v", v) } panics = append(panics, v) } } for i, value := range rawArr { wg.Add(1) go func() { defer handlePanic() defer wg.Done() parseComment(value, &(*arr)[i]) }() } wg.Wait() if len(panics) != 0 { return errors.Join(panics...) } return nil } type commentPost Submission func (post *commentPost) UnmarshalJSON(data []byte) error { *post = commentPost(parseSubmission(gjson.Parse(string(data)))) return nil } type commentApiResponse struct { Data commentArray `json:"data"` }