emoji.go (6654B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package dereferencing 19 20 import ( 21 "context" 22 "fmt" 23 "io" 24 "net/url" 25 26 "github.com/superseriousbusiness/gotosocial/internal/db" 27 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 28 "github.com/superseriousbusiness/gotosocial/internal/id" 29 "github.com/superseriousbusiness/gotosocial/internal/log" 30 "github.com/superseriousbusiness/gotosocial/internal/media" 31 ) 32 33 func (d *deref) GetRemoteEmoji(ctx context.Context, requestingUsername string, remoteURL string, shortcode string, domain string, id string, emojiURI string, ai *media.AdditionalEmojiInfo, refresh bool) (*media.ProcessingEmoji, error) { 34 var ( 35 shortcodeDomain = shortcode + "@" + domain 36 processingEmoji *media.ProcessingEmoji 37 ) 38 39 // Acquire lock for derefs map. 40 unlock := d.derefEmojisMu.Lock() 41 defer unlock() 42 43 // first check if we're already processing this emoji 44 if alreadyProcessing, ok := d.derefEmojis[shortcodeDomain]; ok { 45 // we're already on it, no worries 46 processingEmoji = alreadyProcessing 47 } else { 48 // not processing it yet, let's start 49 t, err := d.transportController.NewTransportForUsername(ctx, requestingUsername) 50 if err != nil { 51 return nil, fmt.Errorf("GetRemoteEmoji: error creating transport to fetch emoji %s: %s", shortcodeDomain, err) 52 } 53 54 derefURI, err := url.Parse(remoteURL) 55 if err != nil { 56 return nil, fmt.Errorf("GetRemoteEmoji: error parsing url for emoji %s: %s", shortcodeDomain, err) 57 } 58 59 dataFunc := func(innerCtx context.Context) (io.ReadCloser, int64, error) { 60 return t.DereferenceMedia(innerCtx, derefURI) 61 } 62 63 newProcessing, err := d.mediaManager.PreProcessEmoji(ctx, dataFunc, shortcode, id, emojiURI, ai, refresh) 64 if err != nil { 65 return nil, fmt.Errorf("GetRemoteEmoji: error processing emoji %s: %s", shortcodeDomain, err) 66 } 67 68 // store it in our map to indicate it's in process 69 d.derefEmojis[shortcodeDomain] = newProcessing 70 processingEmoji = newProcessing 71 } 72 73 // Unlock map. 74 unlock() 75 76 defer func() { 77 // On exit safely remove emoji from map. 78 unlock := d.derefEmojisMu.Lock() 79 delete(d.derefEmojis, shortcodeDomain) 80 unlock() 81 }() 82 83 // Start emoji attachment loading (blocking call). 84 if _, err := processingEmoji.LoadEmoji(ctx); err != nil { 85 return nil, err 86 } 87 88 return processingEmoji, nil 89 } 90 91 func (d *deref) populateEmojis(ctx context.Context, rawEmojis []*gtsmodel.Emoji, requestingUsername string) ([]*gtsmodel.Emoji, error) { 92 // At this point we should know: 93 // * the AP uri of the emoji 94 // * the domain of the emoji 95 // * the shortcode of the emoji 96 // * the remote URL of the image 97 // This should be enough to dereference the emoji 98 99 gotEmojis := make([]*gtsmodel.Emoji, 0, len(rawEmojis)) 100 101 for _, e := range rawEmojis { 102 var gotEmoji *gtsmodel.Emoji 103 var err error 104 shortcodeDomain := e.Shortcode + "@" + e.Domain 105 106 // check if we already know this emoji 107 if e.ID != "" { 108 // we had an ID for this emoji already, which means 109 // it should be fleshed out already and we won't 110 // have to get it from the database again 111 gotEmoji = e 112 } else if gotEmoji, err = d.state.DB.GetEmojiByShortcodeDomain(ctx, e.Shortcode, e.Domain); err != nil && err != db.ErrNoEntries { 113 log.Errorf(ctx, "error checking database for emoji %s: %s", shortcodeDomain, err) 114 continue 115 } 116 117 var refresh bool 118 119 if gotEmoji != nil { 120 // we had the emoji already, but refresh it if necessary 121 if e.UpdatedAt.Unix() > gotEmoji.ImageUpdatedAt.Unix() { 122 log.Tracef(ctx, "emoji %s was updated since we last saw it, will refresh", shortcodeDomain) 123 refresh = true 124 } 125 126 if !refresh && (e.URI != gotEmoji.URI) { 127 log.Tracef(ctx, "emoji %s changed URI since we last saw it, will refresh", shortcodeDomain) 128 refresh = true 129 } 130 131 if !refresh && (e.ImageRemoteURL != gotEmoji.ImageRemoteURL) { 132 log.Tracef(ctx, "emoji %s changed image URL since we last saw it, will refresh", shortcodeDomain) 133 refresh = true 134 } 135 136 if !refresh { 137 log.Tracef(ctx, "emoji %s is up to date, will not refresh", shortcodeDomain) 138 } else { 139 log.Tracef(ctx, "refreshing emoji %s", shortcodeDomain) 140 emojiID := gotEmoji.ID // use existing ID 141 processingEmoji, err := d.GetRemoteEmoji(ctx, requestingUsername, e.ImageRemoteURL, e.Shortcode, e.Domain, emojiID, e.URI, &media.AdditionalEmojiInfo{ 142 Domain: &e.Domain, 143 ImageRemoteURL: &e.ImageRemoteURL, 144 ImageStaticRemoteURL: &e.ImageStaticRemoteURL, 145 Disabled: gotEmoji.Disabled, 146 VisibleInPicker: gotEmoji.VisibleInPicker, 147 }, refresh) 148 if err != nil { 149 log.Errorf(ctx, "couldn't refresh remote emoji %s: %s", shortcodeDomain, err) 150 continue 151 } 152 153 if gotEmoji, err = processingEmoji.LoadEmoji(ctx); err != nil { 154 log.Errorf(ctx, "couldn't load refreshed remote emoji %s: %s", shortcodeDomain, err) 155 continue 156 } 157 } 158 } else { 159 // it's new! go get it! 160 newEmojiID, err := id.NewRandomULID() 161 if err != nil { 162 log.Errorf(ctx, "error generating id for remote emoji %s: %s", shortcodeDomain, err) 163 continue 164 } 165 166 processingEmoji, err := d.GetRemoteEmoji(ctx, requestingUsername, e.ImageRemoteURL, e.Shortcode, e.Domain, newEmojiID, e.URI, &media.AdditionalEmojiInfo{ 167 Domain: &e.Domain, 168 ImageRemoteURL: &e.ImageRemoteURL, 169 ImageStaticRemoteURL: &e.ImageStaticRemoteURL, 170 Disabled: e.Disabled, 171 VisibleInPicker: e.VisibleInPicker, 172 }, refresh) 173 if err != nil { 174 log.Errorf(ctx, "couldn't get remote emoji %s: %s", shortcodeDomain, err) 175 continue 176 } 177 178 if gotEmoji, err = processingEmoji.LoadEmoji(ctx); err != nil { 179 log.Errorf(ctx, "couldn't load remote emoji %s: %s", shortcodeDomain, err) 180 continue 181 } 182 } 183 184 // if we get here, we either had the emoji already or we successfully fetched it 185 gotEmojis = append(gotEmojis, gotEmoji) 186 } 187 188 return gotEmojis, nil 189 }