diff --git a/internal/api/util/parseform.go b/internal/api/util/parseform.go index 3eab065f2..8bb10012c 100644 --- a/internal/api/util/parseform.go +++ b/internal/api/util/parseform.go @@ -18,13 +18,55 @@ package util import ( + "errors" "fmt" "strconv" + "strings" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/util" ) +// ParseFocus parses a media attachment focus parameters from incoming API string. +func ParseFocus(focus string) (focusx, focusy float32, errWithCode gtserror.WithCode) { + if focus == "" { + return + } + spl := strings.Split(focus, ",") + if len(spl) != 2 { + const text = "missing comma separator" + errWithCode = gtserror.NewErrorBadRequest( + errors.New(text), + text, + ) + return + } + xStr := spl[0] + yStr := spl[1] + fx, err := strconv.ParseFloat(xStr, 32) + if err != nil || fx > 1 || fx < -1 { + text := fmt.Sprintf("invalid x focus: %s", xStr) + errWithCode = gtserror.NewErrorBadRequest( + errors.New(text), + text, + ) + return + } + fy, err := strconv.ParseFloat(yStr, 32) + if err != nil || fy > 1 || fy < -1 { + text := fmt.Sprintf("invalid y focus: %s", xStr) + errWithCode = gtserror.NewErrorBadRequest( + errors.New(text), + text, + ) + return + } + focusx = float32(fx) + focusy = float32(fy) + return +} + // ParseDuration parses the given raw interface belonging // the given fieldName as an integer duration. func ParseDuration(rawI any, fieldName string) (*int, error) { diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index c20936692..0a75a4802 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -1197,6 +1197,13 @@ func (d *Dereferencer) handleStatusEdit( // Attached emojis changed. cols = append(cols, "emojis") // i.e. EmojiIDs + // We specifically store both *new* AND *old* edit + // revision emojis in the statuses.emojis column. + emojiByID := func(e *gtsmodel.Emoji) string { return e.ID } + status.Emojis = append(status.Emojis, existing.Emojis...) + status.Emojis = xslices.DeduplicateFunc(status.Emojis, emojiByID) + status.EmojiIDs = xslices.Gather(status.EmojiIDs[:0], status.Emojis, emojiByID) + // Emojis changed doesn't necessarily // indicate an edit, it may just not have // been previously populated properly. diff --git a/internal/processing/media/create.go b/internal/processing/media/create.go index ca1f1c3c6..be2ff5460 100644 --- a/internal/processing/media/create.go +++ b/internal/processing/media/create.go @@ -25,6 +25,7 @@ import ( "codeberg.org/gruf/go-iotools" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -45,10 +46,9 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form } // Parse focus details from API form input. - focusX, focusY, err := parseFocus(form.Focus) - if err != nil { - text := fmt.Sprintf("could not parse focus value %s: %s", form.Focus, err) - return nil, gtserror.NewErrorBadRequest(errors.New(text), text) + focusX, focusY, errWithCode := apiutil.ParseFocus(form.Focus) + if errWithCode != nil { + return nil, errWithCode } // Open multipart file reader. diff --git a/internal/processing/media/update.go b/internal/processing/media/update.go index d3a9cfe61..2348d3ee6 100644 --- a/internal/processing/media/update.go +++ b/internal/processing/media/update.go @@ -23,6 +23,7 @@ import ( "fmt" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -52,9 +53,9 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, media } if form.Focus != nil { - focusx, focusy, err := parseFocus(*form.Focus) + focusx, focusy, errWithCode := apiutil.ParseFocus(*form.Focus) if err != nil { - return nil, gtserror.NewErrorBadRequest(err) + return nil, errWithCode } attachment.FileMeta.Focus.X = focusx attachment.FileMeta.Focus.Y = focusy diff --git a/internal/processing/media/util.go b/internal/processing/media/util.go deleted file mode 100644 index 0ca2697fd..000000000 --- a/internal/processing/media/util.go +++ /dev/null @@ -1,62 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package media - -import ( - "fmt" - "strconv" - "strings" -) - -func parseFocus(focus string) (focusx, focusy float32, err error) { - if focus == "" { - return - } - spl := strings.Split(focus, ",") - if len(spl) != 2 { - err = fmt.Errorf("improperly formatted focus %s", focus) - return - } - xStr := spl[0] - yStr := spl[1] - if xStr == "" || yStr == "" { - err = fmt.Errorf("improperly formatted focus %s", focus) - return - } - fx, err := strconv.ParseFloat(xStr, 32) - if err != nil { - err = fmt.Errorf("improperly formatted focus %s: %s", focus, err) - return - } - if fx > 1 || fx < -1 { - err = fmt.Errorf("improperly formatted focus %s", focus) - return - } - focusx = float32(fx) - fy, err := strconv.ParseFloat(yStr, 32) - if err != nil { - err = fmt.Errorf("improperly formatted focus %s: %s", focus, err) - return - } - if fy > 1 || fy < -1 { - err = fmt.Errorf("improperly formatted focus %s", focus) - return - } - focusy = float32(fy) - return -} diff --git a/internal/processing/status/edit.go b/internal/processing/status/edit.go index c2d14b406..79f541b99 100644 --- a/internal/processing/status/edit.go +++ b/internal/processing/status/edit.go @@ -22,18 +22,18 @@ import ( "errors" "fmt" "slices" - "strconv" - "strings" "time" "github.com/superseriousbusiness/gotosocial/internal/ap" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/messages" + "github.com/superseriousbusiness/gotosocial/internal/util/xslices" ) // Edit ... @@ -222,10 +222,15 @@ func (p *Processor) Edit( } if !slices.Equal(status.EmojiIDs, content.EmojiIDs) { + // We specifically store both *new* AND *old* edit + // revision emojis in the statuses.emojis column. + emojiByID := func(e *gtsmodel.Emoji) string { return e.ID } + status.Emojis = append(status.Emojis, content.Emojis...) + status.Emojis = xslices.DeduplicateFunc(status.Emojis, emojiByID) + status.EmojiIDs = xslices.Gather(status.EmojiIDs[:0], status.Emojis, emojiByID) + // Update attached status emojis. cols = append(cols, "emojis") - status.EmojiIDs = content.EmojiIDs - status.Emojis = content.Emojis } } @@ -391,8 +396,8 @@ func (p *Processor) processMediaEdits( } if attr.Focus != "" { - // Parse provided media focus string request. - fx, fy, errWithCode := parseFocus(attr.Focus) + // Parse provided media focus parameters from string. + fx, fy, errWithCode := apiutil.ParseFocus(attr.Focus) if errWithCode != nil { return false, errWithCode } @@ -544,40 +549,27 @@ func (p *Processor) deletePoll(ctx context.Context, poll *gtsmodel.Poll) error { return nil } -func parseFocus(focus string) (focusx, focusy float32, errWithCode gtserror.WithCode) { - if focus == "" { - return +func deduplicateEmojis(emojis []*gtsmodel.Emoji) ([]*gtsmodel.Emoji, []string) { + set := make(map[string]struct{}, len(emojis)) + + // Store returning emojis, and their IDs. + ret := make([]*gtsmodel.Emoji, 0, len(emojis)) + ids := make([]string, 0, len(emojis)) + for _, emoji := range emojis { + emoji := emoji // rescope + + // Check if already in ID set. + if _, ok := set[emoji.ID]; ok { + continue + } + + // Append emoji to slices. + ret = append(ret, emoji) + ids = append(ids, emoji.ID) + + // Add emoji ID to set. + set[emoji.ID] = struct{}{} } - spl := strings.Split(focus, ",") - if len(spl) != 2 { - const text = "missing comma separator" - errWithCode = gtserror.NewErrorBadRequest( - errors.New(text), - text, - ) - return - } - xStr := spl[0] - yStr := spl[1] - fx, err := strconv.ParseFloat(xStr, 32) - if err != nil || fx > 1 || fx < -1 { - text := fmt.Sprintf("invalid x focus: %s", xStr) - errWithCode = gtserror.NewErrorBadRequest( - errors.New(text), - text, - ) - return - } - fy, err := strconv.ParseFloat(yStr, 32) - if err != nil || fy > 1 || fy < -1 { - text := fmt.Sprintf("invalid y focus: %s", xStr) - errWithCode = gtserror.NewErrorBadRequest( - errors.New(text), - text, - ) - return - } - focusx = float32(fx) - focusy = float32(fy) - return + + return ret, ids }