2023-03-12 15:00:57 +00:00
// 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 <http://www.gnu.org/licenses/>.
2021-08-10 12:32:39 +01:00
package dereferencing
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
2021-08-29 11:03:08 +01:00
"strings"
2021-08-10 12:32:39 +01:00
2021-11-13 16:29:43 +00:00
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
2021-08-10 12:32:39 +01:00
"github.com/superseriousbusiness/gotosocial/internal/ap"
2022-11-29 09:24:55 +00:00
"github.com/superseriousbusiness/gotosocial/internal/config"
2022-09-12 12:03:23 +01:00
"github.com/superseriousbusiness/gotosocial/internal/db"
2021-08-10 12:32:39 +01:00
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
2022-07-19 09:47:55 +01:00
"github.com/superseriousbusiness/gotosocial/internal/log"
2022-01-09 17:41:22 +00:00
"github.com/superseriousbusiness/gotosocial/internal/media"
2023-03-01 17:52:44 +00:00
"github.com/superseriousbusiness/gotosocial/internal/transport"
2021-08-10 12:32:39 +01:00
)
2022-11-29 09:24:55 +00:00
// EnrichRemoteStatus takes a remote status that's already been inserted into the database in a minimal form,
2021-08-10 12:32:39 +01:00
// and populates it with additional fields, media, etc.
//
// EnrichRemoteStatus is mostly useful for calling after a status has been initially created by
// the federatingDB's Create function, but additional dereferencing is needed on it.
2021-09-14 11:23:56 +01:00
func ( d * deref ) EnrichRemoteStatus ( ctx context . Context , username string , status * gtsmodel . Status , includeParent bool ) ( * gtsmodel . Status , error ) {
if err := d . populateStatusFields ( ctx , status , username , includeParent ) ; err != nil {
2021-08-10 12:32:39 +01:00
return nil , err
}
2022-11-15 18:45:15 +00:00
if err := d . db . UpdateStatus ( ctx , status ) ; err != nil {
return nil , err
}
return status , nil
2021-08-10 12:32:39 +01:00
}
2022-11-29 09:24:55 +00:00
// GetStatus completely dereferences a status, converts it to a GtS model status,
2022-05-23 16:40:03 +01:00
// puts it in the database, and returns it to a caller.
2021-08-10 12:32:39 +01:00
//
2022-05-23 16:40:03 +01:00
// If refetch is true, then regardless of whether we have the original status in the database or not,
// the ap.Statusable representation of the status will be dereferenced and returned.
2021-08-10 12:32:39 +01:00
//
2022-05-23 16:40:03 +01:00
// If refetch is false, the ap.Statusable will only be returned if this is a new status, so callers
// should check whether or not this is nil.
2021-08-10 12:32:39 +01:00
//
2022-11-29 09:24:55 +00:00
// GetAccount will guard against trying to do http calls to fetch a status that belongs to this instance.
// Instead of making calls, it will just return the status early if it finds it, or return an error.
func ( d * deref ) GetStatus ( ctx context . Context , username string , statusURI * url . URL , refetch , includeParent bool ) ( * gtsmodel . Status , ap . Statusable , error ) {
uriString := statusURI . String ( )
// try to get by URI first
status , dbErr := d . db . GetStatusByURI ( ctx , uriString )
if dbErr != nil {
if ! errors . Is ( dbErr , db . ErrNoEntries ) {
// real error
return nil , nil , newErrDB ( fmt . Errorf ( "GetRemoteStatus: error during GetStatusByURI for %s: %w" , uriString , dbErr ) )
}
// no problem, just press on
} else if ! refetch {
2022-05-23 16:40:03 +01:00
// we already had the status and we aren't being asked to refetch the AP representation
2022-11-29 09:24:55 +00:00
return status , nil , nil
2021-08-10 12:32:39 +01:00
}
2022-11-29 09:24:55 +00:00
// try to get by URL if we couldn't get by URI now
if status == nil {
status , dbErr = d . db . GetStatusByURL ( ctx , uriString )
if dbErr != nil {
if ! errors . Is ( dbErr , db . ErrNoEntries ) {
// real error
return nil , nil , newErrDB ( fmt . Errorf ( "GetRemoteStatus: error during GetStatusByURI for %s: %w" , uriString , dbErr ) )
}
// no problem, just press on
} else if ! refetch {
// we already had the status and we aren't being asked to refetch the AP representation
return status , nil , nil
}
2022-05-23 16:40:03 +01:00
}
2022-11-29 09:24:55 +00:00
// guard against having our own statuses passed in
if host := statusURI . Host ; host == config . GetHost ( ) || host == config . GetAccountDomain ( ) {
// this is our status, definitely don't search for it
if status != nil {
return status , nil , nil
}
2023-02-03 20:03:05 +00:00
return nil , nil , NewErrNotRetrievable ( fmt . Errorf ( "GetRemoteStatus: uri %s is apparently ours, but we have nothing in the db for it, will not proceed to dereference our own status" , uriString ) )
2022-11-29 09:24:55 +00:00
}
// if we got here, either we didn't have the status
// in the db, or we had it but need to refetch it
2023-03-01 17:52:44 +00:00
tsport , err := d . transportController . NewTransportForUsername ( ctx , username )
if err != nil {
return nil , nil , newErrTransportError ( fmt . Errorf ( "GetRemoteStatus: error creating transport for %s: %w" , username , err ) )
}
statusable , derefErr := d . dereferenceStatusable ( ctx , tsport , statusURI )
2022-11-29 09:24:55 +00:00
if derefErr != nil {
return nil , nil , wrapDerefError ( derefErr , "GetRemoteStatus: error dereferencing statusable" )
}
if status != nil && refetch {
// we already had the status in the db, and we've also
// now fetched the AP representation as requested
return status , statusable , nil
2021-08-10 12:32:39 +01:00
}
2022-05-23 16:40:03 +01:00
// from here on out we can consider this to be a 'new' status because we didn't have the status in the db already
2021-08-10 12:32:39 +01:00
accountURI , err := ap . ExtractAttributedTo ( statusable )
if err != nil {
2022-11-29 09:24:55 +00:00
return nil , nil , newErrOther ( fmt . Errorf ( "GetRemoteStatus: error extracting attributedTo: %w" , err ) )
2021-08-10 12:32:39 +01:00
}
2022-11-29 09:24:55 +00:00
// we need to get the author of the status else we can't serialize it properly
2023-02-03 20:03:05 +00:00
if _ , err = d . GetAccountByURI ( ctx , username , accountURI , true ) ; err != nil {
2022-11-29 09:24:55 +00:00
return nil , nil , newErrOther ( fmt . Errorf ( "GetRemoteStatus: couldn't get status author: %s" , err ) )
2021-08-10 12:32:39 +01:00
}
2022-11-29 09:24:55 +00:00
status , err = d . typeConverter . ASStatusToStatus ( ctx , statusable )
2021-08-10 12:32:39 +01:00
if err != nil {
2022-11-29 09:24:55 +00:00
return nil , nil , newErrOther ( fmt . Errorf ( "GetRemoteStatus: error converting statusable to status: %s" , err ) )
2021-08-10 12:32:39 +01:00
}
2022-11-29 09:24:55 +00:00
ulid , err := id . NewULIDFromTime ( status . CreatedAt )
2022-05-23 16:40:03 +01:00
if err != nil {
2022-11-29 09:24:55 +00:00
return nil , nil , newErrOther ( fmt . Errorf ( "GetRemoteStatus: error generating new id for status: %s" , err ) )
2022-05-23 16:40:03 +01:00
}
2022-11-29 09:24:55 +00:00
status . ID = ulid
2021-08-10 12:32:39 +01:00
2022-11-29 09:24:55 +00:00
if err := d . populateStatusFields ( ctx , status , username , includeParent ) ; err != nil {
return nil , nil , newErrOther ( fmt . Errorf ( "GetRemoteStatus: error populating status fields: %s" , err ) )
2022-05-23 16:40:03 +01:00
}
2021-08-10 12:32:39 +01:00
2022-11-29 09:24:55 +00:00
if err := d . db . PutStatus ( ctx , status ) ; err != nil && ! errors . Is ( err , db . ErrAlreadyExists ) {
return nil , nil , newErrDB ( fmt . Errorf ( "GetRemoteStatus: error putting new status: %s" , err ) )
2021-08-10 12:32:39 +01:00
}
2022-11-29 09:24:55 +00:00
return status , statusable , nil
2021-08-10 12:32:39 +01:00
}
2023-03-01 17:52:44 +00:00
func ( d * deref ) dereferenceStatusable ( ctx context . Context , tsport transport . Transport , remoteStatusID * url . URL ) ( ap . Statusable , error ) {
2021-08-25 14:34:33 +01:00
if blocked , err := d . db . IsDomainBlocked ( ctx , remoteStatusID . Host ) ; blocked || err != nil {
2021-08-10 12:32:39 +01:00
return nil , fmt . Errorf ( "DereferenceStatusable: domain %s is blocked" , remoteStatusID . Host )
}
2023-03-01 17:52:44 +00:00
b , err := tsport . Dereference ( ctx , remoteStatusID )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , fmt . Errorf ( "DereferenceStatusable: error deferencing %s: %s" , remoteStatusID . String ( ) , err )
}
m := make ( map [ string ] interface { } )
if err := json . Unmarshal ( b , & m ) ; err != nil {
return nil , fmt . Errorf ( "DereferenceStatusable: error unmarshalling bytes into json: %s" , err )
}
2021-10-04 14:24:19 +01:00
t , err := streams . ToType ( ctx , m )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , fmt . Errorf ( "DereferenceStatusable: error resolving json into ap vocab type: %s" , err )
}
// Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile
switch t . GetTypeName ( ) {
2021-08-31 14:59:12 +01:00
case ap . ObjectArticle :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsArticle )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsArticle" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectDocument :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsDocument )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsDocument" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectImage :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsImage )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsImage" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectVideo :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsVideo )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsVideo" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectNote :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsNote )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsNote" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectPage :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsPage )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsPage" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectEvent :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsEvent )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsEvent" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectPlace :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsPlace )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsPlace" )
}
return p , nil
2021-08-31 14:59:12 +01:00
case ap . ObjectProfile :
2021-08-10 12:32:39 +01:00
p , ok := t . ( vocab . ActivityStreamsProfile )
if ! ok {
return nil , errors . New ( "DereferenceStatusable: error resolving type as ActivityStreamsProfile" )
}
return p , nil
}
2022-11-29 09:24:55 +00:00
return nil , newErrWrongType ( fmt . Errorf ( "DereferenceStatusable: type name %s not supported as Statusable" , t . GetTypeName ( ) ) )
2021-08-10 12:32:39 +01:00
}
// populateStatusFields fetches all the information we temporarily pinned to an incoming
// federated status, back in the federating db's Create function.
//
// When a status comes in from the federation API, there are certain fields that
// haven't been dereferenced yet, because we needed to provide a snappy synchronous
// response to the caller. By the time it reaches this function though, it's being
// processed asynchronously, so we have all the time in the world to fetch the various
// bits and bobs that are attached to the status, and properly flesh it out, before we
// send the status to any timelines and notify people.
//
// Things to dereference and fetch here:
//
// 1. Media attachments.
// 2. Hashtags.
// 3. Emojis.
// 4. Mentions.
2021-08-29 11:03:08 +01:00
// 5. Replied-to-status.
2021-08-10 12:32:39 +01:00
//
// SIDE EFFECTS:
// This function will deference all of the above, insert them in the database as necessary,
// and attach them to the status. The status itself will not be added to the database yet,
// that's up the caller to do.
2021-09-14 11:23:56 +01:00
func ( d * deref ) populateStatusFields ( ctx context . Context , status * gtsmodel . Status , requestingUsername string , includeParent bool ) error {
2021-08-29 11:03:08 +01:00
statusIRI , err := url . Parse ( status . URI )
2021-08-10 12:32:39 +01:00
if err != nil {
2021-08-29 11:03:08 +01:00
return fmt . Errorf ( "populateStatusFields: couldn't parse status URI %s: %s" , status . URI , err )
2021-08-10 12:32:39 +01:00
}
2021-08-29 11:03:08 +01:00
blocked , err := d . db . IsURIBlocked ( ctx , statusIRI )
2021-08-10 12:32:39 +01:00
if err != nil {
2021-08-29 11:03:08 +01:00
return fmt . Errorf ( "populateStatusFields: error checking blocked status of %s: %s" , statusIRI , err )
}
if blocked {
return fmt . Errorf ( "populateStatusFields: domain %s is blocked" , statusIRI )
2021-08-10 12:32:39 +01:00
}
// in case the status doesn't have an id yet (ie., it hasn't entered the database yet), then create one
if status . ID == "" {
newID , err := id . NewULIDFromTime ( status . CreatedAt )
if err != nil {
2021-08-29 11:03:08 +01:00
return fmt . Errorf ( "populateStatusFields: error creating ulid for status: %s" , err )
2021-08-10 12:32:39 +01:00
}
status . ID = newID
}
// 1. Media attachments.
2021-08-29 11:03:08 +01:00
if err := d . populateStatusAttachments ( ctx , status , requestingUsername ) ; err != nil {
return fmt . Errorf ( "populateStatusFields: error populating status attachments: %s" , err )
2021-08-10 12:32:39 +01:00
}
// 2. Hashtags
2021-08-29 11:03:08 +01:00
// TODO
2021-08-10 12:32:39 +01:00
// 3. Emojis
2022-09-12 12:03:23 +01:00
if err := d . populateStatusEmojis ( ctx , status , requestingUsername ) ; err != nil {
return fmt . Errorf ( "populateStatusFields: error populating status emojis: %s" , err )
}
2021-08-10 12:32:39 +01:00
2021-09-14 11:23:56 +01:00
// 4. Mentions
2021-09-01 10:08:21 +01:00
// TODO: do we need to handle removing empty mention objects and just using mention IDs slice?
2021-09-14 11:23:56 +01:00
if err := d . populateStatusMentions ( ctx , status , requestingUsername ) ; err != nil {
return fmt . Errorf ( "populateStatusFields: error populating status mentions: %s" , err )
2021-08-29 11:03:08 +01:00
}
2021-09-01 10:08:21 +01:00
// 5. Replied-to-status (only if requested)
if includeParent {
if err := d . populateStatusRepliedTo ( ctx , status , requestingUsername ) ; err != nil {
return fmt . Errorf ( "populateStatusFields: error populating status repliedTo: %s" , err )
}
2021-08-29 11:03:08 +01:00
}
return nil
}
func ( d * deref ) populateStatusMentions ( ctx context . Context , status * gtsmodel . Status , requestingUsername string ) error {
2021-08-10 12:32:39 +01:00
// At this point, mentions should have the namestring and mentionedAccountURI set on them.
2021-08-29 11:03:08 +01:00
// We can use these to find the accounts.
2021-08-20 11:26:56 +01:00
mentionIDs := [ ] string { }
2021-08-29 11:03:08 +01:00
newMentions := [ ] * gtsmodel . Mention { }
2021-08-20 11:26:56 +01:00
for _ , m := range status . Mentions {
2021-08-10 12:32:39 +01:00
if m . ID != "" {
// we've already populated this mention, since it has an ID
2023-02-17 11:02:29 +00:00
log . Debug ( ctx , "mention already populated" )
2021-08-29 11:03:08 +01:00
mentionIDs = append ( mentionIDs , m . ID )
newMentions = append ( newMentions , m )
2021-08-20 11:26:56 +01:00
continue
2021-08-10 12:32:39 +01:00
}
2021-08-20 11:26:56 +01:00
if m . TargetAccountURI == "" {
2023-02-17 11:02:29 +00:00
log . Debug ( ctx , "target URI not set on mention" )
2021-08-20 11:26:56 +01:00
continue
2021-08-10 12:32:39 +01:00
}
2021-08-20 11:26:56 +01:00
targetAccountURI , err := url . Parse ( m . TargetAccountURI )
2021-08-10 12:32:39 +01:00
if err != nil {
2023-02-17 11:02:29 +00:00
log . Debugf ( ctx , "error parsing mentioned account uri %s: %s" , m . TargetAccountURI , err )
2021-08-10 12:32:39 +01:00
continue
}
2021-08-20 11:26:56 +01:00
var targetAccount * gtsmodel . Account
2021-08-29 11:03:08 +01:00
errs := [ ] string { }
// check if account is in the db already
2021-09-14 11:23:56 +01:00
if a , err := d . db . GetAccountByURI ( ctx , targetAccountURI . String ( ) ) ; err != nil {
2021-08-29 11:03:08 +01:00
errs = append ( errs , err . Error ( ) )
2021-08-20 11:26:56 +01:00
} else {
2023-02-17 11:02:29 +00:00
log . Debugf ( ctx , "got target account %s with id %s through GetAccountByURI" , targetAccountURI , a . ID )
2021-08-29 11:03:08 +01:00
targetAccount = a
}
if targetAccount == nil {
// we didn't find the account in our database already
// check if we can get the account remotely (dereference it)
2023-02-03 20:03:05 +00:00
if a , err := d . GetAccountByURI ( ctx , requestingUsername , targetAccountURI , false ) ; err != nil {
2021-08-29 11:03:08 +01:00
errs = append ( errs , err . Error ( ) )
} else {
2023-02-17 11:02:29 +00:00
log . Debugf ( ctx , "got target account %s with id %s through GetRemoteAccount" , targetAccountURI , a . ID )
2021-08-29 11:03:08 +01:00
targetAccount = a
}
}
if targetAccount == nil {
2023-02-17 11:02:29 +00:00
log . Debugf ( ctx , "couldn't get target account %s: %s" , m . TargetAccountURI , strings . Join ( errs , " : " ) )
2021-08-20 11:26:56 +01:00
continue
}
2021-08-10 12:32:39 +01:00
2021-08-20 11:26:56 +01:00
mID , err := id . NewRandomULID ( )
2021-08-10 12:32:39 +01:00
if err != nil {
2021-08-29 11:03:08 +01:00
return fmt . Errorf ( "populateStatusMentions: error generating ulid: %s" , err )
2021-08-20 11:26:56 +01:00
}
2021-08-29 11:03:08 +01:00
newMention := & gtsmodel . Mention {
2021-08-20 11:26:56 +01:00
ID : mID ,
StatusID : status . ID ,
Status : m . Status ,
CreatedAt : status . CreatedAt ,
UpdatedAt : status . UpdatedAt ,
2021-09-14 11:23:56 +01:00
OriginAccountID : status . AccountID ,
2021-08-20 11:26:56 +01:00
OriginAccountURI : status . AccountURI ,
OriginAccount : status . Account ,
TargetAccountID : targetAccount . ID ,
TargetAccount : targetAccount ,
NameString : m . NameString ,
TargetAccountURI : targetAccount . URI ,
TargetAccountURL : targetAccount . URL ,
2021-08-10 12:32:39 +01:00
}
[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
2023-03-28 14:03:14 +01:00
if err := d . db . PutMention ( ctx , newMention ) ; err != nil {
2021-08-29 11:03:08 +01:00
return fmt . Errorf ( "populateStatusMentions: error creating mention: %s" , err )
2021-08-10 12:32:39 +01:00
}
2021-08-29 11:03:08 +01:00
mentionIDs = append ( mentionIDs , newMention . ID )
newMentions = append ( newMentions , newMention )
2021-08-10 12:32:39 +01:00
}
2021-08-29 11:03:08 +01:00
2021-08-20 11:26:56 +01:00
status . MentionIDs = mentionIDs
2021-08-29 11:03:08 +01:00
status . Mentions = newMentions
2021-08-10 12:32:39 +01:00
2021-08-29 11:03:08 +01:00
return nil
}
func ( d * deref ) populateStatusAttachments ( ctx context . Context , status * gtsmodel . Status , requestingUsername string ) error {
// At this point we should know:
// * the media type of the file we're looking for (a.File.ContentType)
// * the file type (a.Type)
// * the remote URL (a.RemoteURL)
// This should be enough to dereference the piece of media.
attachmentIDs := [ ] string { }
attachments := [ ] * gtsmodel . MediaAttachment { }
for _ , a := range status . Attachments {
2021-09-04 13:02:01 +01:00
a . AccountID = status . AccountID
a . StatusID = status . ID
2021-08-29 11:03:08 +01:00
2022-01-24 12:12:17 +00:00
processingMedia , err := d . GetRemoteMedia ( ctx , requestingUsername , a . AccountID , a . RemoteURL , & media . AdditionalMediaInfo {
2022-01-09 17:41:22 +00:00
CreatedAt : & a . CreatedAt ,
StatusID : & a . StatusID ,
RemoteURL : & a . RemoteURL ,
Description : & a . Description ,
Blurhash : & a . Blurhash ,
} )
2021-08-29 11:03:08 +01:00
if err != nil {
2023-02-17 11:02:29 +00:00
log . Errorf ( ctx , "couldn't get remote media %s: %s" , a . RemoteURL , err )
2022-01-08 16:17:01 +00:00
continue
}
2022-01-24 12:12:17 +00:00
attachment , err := processingMedia . LoadAttachment ( ctx )
2022-01-08 16:17:01 +00:00
if err != nil {
2023-02-17 11:02:29 +00:00
log . Errorf ( ctx , "couldn't load remote attachment %s: %s" , a . RemoteURL , err )
2021-09-01 10:08:21 +01:00
continue
2021-08-29 11:03:08 +01:00
}
attachmentIDs = append ( attachmentIDs , attachment . ID )
attachments = append ( attachments , attachment )
}
status . AttachmentIDs = attachmentIDs
status . Attachments = attachments
return nil
}
2022-09-12 12:03:23 +01:00
func ( d * deref ) populateStatusEmojis ( ctx context . Context , status * gtsmodel . Status , requestingUsername string ) error {
2022-09-26 10:56:01 +01:00
emojis , err := d . populateEmojis ( ctx , status . Emojis , requestingUsername )
if err != nil {
return err
}
2022-09-12 12:03:23 +01:00
2022-09-26 10:56:01 +01:00
emojiIDs := make ( [ ] string , 0 , len ( emojis ) )
for _ , e := range emojis {
emojiIDs = append ( emojiIDs , e . ID )
2022-09-12 12:03:23 +01:00
}
2022-09-26 10:56:01 +01:00
status . Emojis = emojis
2022-09-12 12:03:23 +01:00
status . EmojiIDs = emojiIDs
return nil
}
2021-08-29 11:03:08 +01:00
func ( d * deref ) populateStatusRepliedTo ( ctx context . Context , status * gtsmodel . Status , requestingUsername string ) error {
2021-08-10 12:32:39 +01:00
if status . InReplyToURI != "" && status . InReplyToID == "" {
2021-08-20 11:26:56 +01:00
statusURI , err := url . Parse ( status . InReplyToURI )
if err != nil {
return err
}
2021-08-29 11:03:08 +01:00
2022-11-29 09:24:55 +00:00
replyToStatus , _ , err := d . GetStatus ( ctx , requestingUsername , statusURI , false , false )
2021-09-01 10:08:21 +01:00
if err != nil {
2022-05-23 16:40:03 +01:00
return fmt . Errorf ( "populateStatusRepliedTo: couldn't get reply to status with uri %s: %s" , status . InReplyToURI , err )
2021-08-10 12:32:39 +01:00
}
2021-08-29 11:03:08 +01:00
// we have the status
status . InReplyToID = replyToStatus . ID
status . InReplyTo = replyToStatus
status . InReplyToAccountID = replyToStatus . AccountID
status . InReplyToAccount = replyToStatus . Account
2021-08-10 12:32:39 +01:00
}
2021-08-29 11:03:08 +01:00
2021-08-10 12:32:39 +01:00
return nil
}