gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

astointernal.go (25283B)


      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 typeutils
     19 
     20 import (
     21 	"context"
     22 	"errors"
     23 	"fmt"
     24 	"net/url"
     25 
     26 	"github.com/miekg/dns"
     27 	"github.com/superseriousbusiness/gotosocial/internal/ap"
     28 	"github.com/superseriousbusiness/gotosocial/internal/config"
     29 	"github.com/superseriousbusiness/gotosocial/internal/db"
     30 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
     31 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     32 	"github.com/superseriousbusiness/gotosocial/internal/log"
     33 	"github.com/superseriousbusiness/gotosocial/internal/uris"
     34 )
     35 
     36 func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable ap.Accountable, accountDomain string) (*gtsmodel.Account, error) {
     37 	// first check if we actually already know this account
     38 	uriProp := accountable.GetJSONLDId()
     39 	if uriProp == nil || !uriProp.IsIRI() {
     40 		return nil, errors.New("no id property found on person, or id was not an iri")
     41 	}
     42 	uri := uriProp.GetIRI()
     43 
     44 	// we don't know the account, or we're being told to update it, so we need to generate it from the person -- at least we already have the URI!
     45 	acct := &gtsmodel.Account{}
     46 	acct.URI = uri.String()
     47 
     48 	// Username aka preferredUsername
     49 	// We need this one so bail if it's not set.
     50 	username, err := ap.ExtractPreferredUsername(accountable)
     51 	if err != nil {
     52 		return nil, fmt.Errorf("couldn't extract username: %s", err)
     53 	}
     54 	acct.Username = username
     55 
     56 	// Domain
     57 	if accountDomain != "" {
     58 		acct.Domain = accountDomain
     59 	} else {
     60 		acct.Domain = uri.Host
     61 	}
     62 
     63 	// avatar aka icon
     64 	// if this one isn't extractable in a format we recognise we'll just skip it
     65 	if avatarURL, err := ap.ExtractIconURI(accountable); err == nil {
     66 		acct.AvatarRemoteURL = avatarURL.String()
     67 	}
     68 
     69 	// header aka image
     70 	// if this one isn't extractable in a format we recognise we'll just skip it
     71 	if headerURL, err := ap.ExtractImageURI(accountable); err == nil {
     72 		acct.HeaderRemoteURL = headerURL.String()
     73 	}
     74 
     75 	// display name aka name
     76 	// we default to the username, but take the more nuanced name property if it exists
     77 	if displayName := ap.ExtractName(accountable); displayName != "" {
     78 		acct.DisplayName = displayName
     79 	} else {
     80 		acct.DisplayName = username
     81 	}
     82 
     83 	// account emojis (used in bio, display name, fields)
     84 	if emojis, err := ap.ExtractEmojis(accountable); err != nil {
     85 		log.Infof(nil, "error extracting account emojis: %s", err)
     86 	} else {
     87 		acct.Emojis = emojis
     88 	}
     89 
     90 	// fields aka attachment array
     91 	acct.Fields = ap.ExtractFields(accountable)
     92 
     93 	// note aka summary
     94 	acct.Note = ap.ExtractSummary(accountable)
     95 
     96 	// check for bot and actor type
     97 	switch accountable.GetTypeName() {
     98 	case ap.ActorPerson, ap.ActorGroup, ap.ActorOrganization:
     99 		// people, groups, and organizations aren't bots
    100 		bot := false
    101 		acct.Bot = &bot
    102 		// apps and services are
    103 	case ap.ActorApplication, ap.ActorService:
    104 		bot := true
    105 		acct.Bot = &bot
    106 	default:
    107 		// we don't know what this is!
    108 		return nil, fmt.Errorf("type name %s not recognised or not convertible to ap.ActivityStreamsActor", accountable.GetTypeName())
    109 	}
    110 	acct.ActorType = accountable.GetTypeName()
    111 
    112 	// assume not memorial (todo)
    113 	memorial := false
    114 	acct.Memorial = &memorial
    115 
    116 	// assume not sensitive (todo)
    117 	sensitive := false
    118 	acct.Sensitive = &sensitive
    119 
    120 	// assume not hide collections (todo)
    121 	hideCollections := false
    122 	acct.HideCollections = &hideCollections
    123 
    124 	// locked aka manuallyApprovesFollowers
    125 	locked := true
    126 	acct.Locked = &locked // assume locked by default
    127 	maf := accountable.GetActivityStreamsManuallyApprovesFollowers()
    128 	if maf != nil && maf.IsXMLSchemaBoolean() {
    129 		locked = maf.Get()
    130 		acct.Locked = &locked
    131 	}
    132 
    133 	// discoverable
    134 	// default to false -- take custom value if it's set though
    135 	discoverable := false
    136 	acct.Discoverable = &discoverable
    137 	d, err := ap.ExtractDiscoverable(accountable)
    138 	if err == nil {
    139 		acct.Discoverable = &d
    140 	}
    141 
    142 	// assume not rss feed
    143 	enableRSS := false
    144 	acct.EnableRSS = &enableRSS
    145 
    146 	// url property
    147 	url, err := ap.ExtractURL(accountable)
    148 	if err == nil {
    149 		// take the URL if we can find it
    150 		acct.URL = url.String()
    151 	} else {
    152 		// otherwise just take the account URI as the URL
    153 		acct.URL = uri.String()
    154 	}
    155 
    156 	// InboxURI
    157 	if accountable.GetActivityStreamsInbox() != nil && accountable.GetActivityStreamsInbox().GetIRI() != nil {
    158 		acct.InboxURI = accountable.GetActivityStreamsInbox().GetIRI().String()
    159 	}
    160 
    161 	// SharedInboxURI:
    162 	// only trust shared inbox if it has at least two domains,
    163 	// from the right, in common with the domain of the account
    164 	if sharedInboxURI := ap.ExtractSharedInbox(accountable); // nocollapse
    165 	sharedInboxURI != nil && dns.CompareDomainName(acct.Domain, sharedInboxURI.Host) >= 2 {
    166 		sharedInbox := sharedInboxURI.String()
    167 		acct.SharedInboxURI = &sharedInbox
    168 	}
    169 
    170 	// OutboxURI
    171 	if accountable.GetActivityStreamsOutbox() != nil && accountable.GetActivityStreamsOutbox().GetIRI() != nil {
    172 		acct.OutboxURI = accountable.GetActivityStreamsOutbox().GetIRI().String()
    173 	}
    174 
    175 	// FollowingURI
    176 	if accountable.GetActivityStreamsFollowing() != nil && accountable.GetActivityStreamsFollowing().GetIRI() != nil {
    177 		acct.FollowingURI = accountable.GetActivityStreamsFollowing().GetIRI().String()
    178 	}
    179 
    180 	// FollowersURI
    181 	if accountable.GetActivityStreamsFollowers() != nil && accountable.GetActivityStreamsFollowers().GetIRI() != nil {
    182 		acct.FollowersURI = accountable.GetActivityStreamsFollowers().GetIRI().String()
    183 	}
    184 
    185 	// FeaturedURI aka pinned collection:
    186 	// Only trust featured URI if it has at least two domains,
    187 	// from the right, in common with the domain of the account
    188 	if featured := accountable.GetTootFeatured(); featured != nil && featured.IsIRI() {
    189 		if featuredURI := featured.GetIRI(); // nocollapse
    190 		featuredURI != nil && dns.CompareDomainName(acct.Domain, featuredURI.Host) >= 2 {
    191 			acct.FeaturedCollectionURI = featuredURI.String()
    192 		}
    193 	}
    194 
    195 	// TODO: FeaturedTagsURI
    196 
    197 	// TODO: alsoKnownAs
    198 
    199 	// publicKey
    200 	pkey, pkeyURL, pkeyOwnerID, err := ap.ExtractPublicKey(accountable)
    201 	if err != nil {
    202 		return nil, fmt.Errorf("couldn't get public key for person %s: %s", uri.String(), err)
    203 	}
    204 
    205 	if pkeyOwnerID.String() != acct.URI {
    206 		return nil, fmt.Errorf("public key %s was owned by %s and not by %s", pkeyURL, pkeyOwnerID, acct.URI)
    207 	}
    208 
    209 	acct.PublicKey = pkey
    210 	acct.PublicKeyURI = pkeyURL.String()
    211 
    212 	return acct, nil
    213 }
    214 
    215 func (c *converter) extractAttachments(i ap.WithAttachment) []*gtsmodel.MediaAttachment {
    216 	attachmentProp := i.GetActivityStreamsAttachment()
    217 	if attachmentProp == nil {
    218 		return nil
    219 	}
    220 
    221 	attachments := make([]*gtsmodel.MediaAttachment, 0, attachmentProp.Len())
    222 
    223 	for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() {
    224 		t := iter.GetType()
    225 		if t == nil {
    226 			continue
    227 		}
    228 
    229 		attachmentable, ok := t.(ap.Attachmentable)
    230 		if !ok {
    231 			log.Error(nil, "ap attachment was not attachmentable")
    232 			continue
    233 		}
    234 
    235 		attachment, err := ap.ExtractAttachment(attachmentable)
    236 		if err != nil {
    237 			log.Errorf(nil, "error extracting attachment: %s", err)
    238 			continue
    239 		}
    240 
    241 		attachments = append(attachments, attachment)
    242 	}
    243 
    244 	return attachments
    245 }
    246 
    247 func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusable) (*gtsmodel.Status, error) {
    248 	status := &gtsmodel.Status{}
    249 
    250 	// uri at which this status is reachable
    251 	uriProp := statusable.GetJSONLDId()
    252 	if uriProp == nil || !uriProp.IsIRI() {
    253 		return nil, errors.New("no id property found, or id was not an iri")
    254 	}
    255 	status.URI = uriProp.GetIRI().String()
    256 
    257 	l := log.WithContext(ctx).
    258 		WithField("statusURI", status.URI)
    259 
    260 	// web url for viewing this status
    261 	if statusURL, err := ap.ExtractURL(statusable); err == nil {
    262 		status.URL = statusURL.String()
    263 	} else {
    264 		// if no URL was set, just take the URI
    265 		status.URL = status.URI
    266 	}
    267 
    268 	// the html-formatted content of this status
    269 	status.Content = ap.ExtractContent(statusable)
    270 
    271 	// attachments to dereference and fetch later on (we don't do that here)
    272 	status.Attachments = c.extractAttachments(statusable)
    273 
    274 	// hashtags to dereference later on
    275 	if hashtags, err := ap.ExtractHashtags(statusable); err != nil {
    276 		l.Infof("ASStatusToStatus: error extracting status hashtags: %s", err)
    277 	} else {
    278 		status.Tags = hashtags
    279 	}
    280 
    281 	// emojis to dereference and fetch later on
    282 	if emojis, err := ap.ExtractEmojis(statusable); err != nil {
    283 		l.Infof("ASStatusToStatus: error extracting status emojis: %s", err)
    284 	} else {
    285 		status.Emojis = emojis
    286 	}
    287 
    288 	// mentions to dereference later on
    289 	if mentions, err := ap.ExtractMentions(statusable); err != nil {
    290 		l.Infof("ASStatusToStatus: error extracting status mentions: %s", err)
    291 	} else {
    292 		status.Mentions = mentions
    293 	}
    294 
    295 	// cw string for this status
    296 	// prefer Summary, fall back to Name
    297 	if summary := ap.ExtractSummary(statusable); summary != "" {
    298 		status.ContentWarning = summary
    299 	} else {
    300 		status.ContentWarning = ap.ExtractName(statusable)
    301 	}
    302 
    303 	// when was this status created?
    304 	published, err := ap.ExtractPublished(statusable)
    305 	if err != nil {
    306 		l.Infof("ASStatusToStatus: error extracting status published: %s", err)
    307 	} else {
    308 		status.CreatedAt = published
    309 		status.UpdatedAt = published
    310 	}
    311 
    312 	// which account posted this status?
    313 	// if we don't know the account yet we can dereference it later
    314 	attributedTo, err := ap.ExtractAttributedToURI(statusable)
    315 	if err != nil {
    316 		return nil, errors.New("ASStatusToStatus: attributedTo was empty")
    317 	}
    318 	status.AccountURI = attributedTo.String()
    319 
    320 	statusOwner, err := c.db.GetAccountByURI(ctx, attributedTo.String())
    321 	if err != nil {
    322 		return nil, fmt.Errorf("ASStatusToStatus: couldn't get status owner from db: %s", err)
    323 	}
    324 	status.AccountID = statusOwner.ID
    325 	status.AccountURI = statusOwner.URI
    326 	status.Account = statusOwner
    327 
    328 	// check if there's a post that this is a reply to
    329 	inReplyToURI := ap.ExtractInReplyToURI(statusable)
    330 	if inReplyToURI != nil {
    331 		// something is set so we can at least set this field on the
    332 		// status and dereference using this later if we need to
    333 		status.InReplyToURI = inReplyToURI.String()
    334 
    335 		// now we can check if we have the replied-to status in our db already
    336 		if inReplyToStatus, err := c.db.GetStatusByURI(ctx, inReplyToURI.String()); err == nil {
    337 			// we have the status in our database already
    338 			// so we can set these fields here and now...
    339 			status.InReplyToID = inReplyToStatus.ID
    340 			status.InReplyToAccountID = inReplyToStatus.AccountID
    341 			status.InReplyTo = inReplyToStatus
    342 			if status.InReplyToAccount == nil {
    343 				if inReplyToAccount, err := c.db.GetAccountByID(ctx, inReplyToStatus.AccountID); err == nil {
    344 					status.InReplyToAccount = inReplyToAccount
    345 				}
    346 			}
    347 		}
    348 	}
    349 
    350 	// visibility entry for this status
    351 	visibility, err := ap.ExtractVisibility(statusable, status.Account.FollowersURI)
    352 	if err != nil {
    353 		return nil, fmt.Errorf("ASStatusToStatus: error extracting visibility: %s", err)
    354 	}
    355 	status.Visibility = visibility
    356 
    357 	// advanced visibility for this status
    358 	// TODO: a lot of work to be done here -- a new type needs to be created for this in go-fed/activity using ASTOOL
    359 	// for now we just set everything to true
    360 	federated := true
    361 	boostable := true
    362 	replyable := true
    363 	likeable := true
    364 
    365 	status.Federated = &federated
    366 	status.Boostable = &boostable
    367 	status.Replyable = &replyable
    368 	status.Likeable = &likeable
    369 
    370 	// sensitive
    371 	sensitive := ap.ExtractSensitive(statusable)
    372 	status.Sensitive = &sensitive
    373 
    374 	// language
    375 	// we might be able to extract this from the contentMap field
    376 
    377 	// ActivityStreamsType
    378 	status.ActivityStreamsType = statusable.GetTypeName()
    379 
    380 	return status, nil
    381 }
    382 
    383 func (c *converter) ASFollowToFollowRequest(ctx context.Context, followable ap.Followable) (*gtsmodel.FollowRequest, error) {
    384 	idProp := followable.GetJSONLDId()
    385 	if idProp == nil || !idProp.IsIRI() {
    386 		return nil, errors.New("no id property set on follow, or was not an iri")
    387 	}
    388 	uri := idProp.GetIRI().String()
    389 
    390 	origin, err := ap.ExtractActorURI(followable)
    391 	if err != nil {
    392 		return nil, errors.New("error extracting actor property from follow")
    393 	}
    394 	originAccount, err := c.db.GetAccountByURI(ctx, origin.String())
    395 	if err != nil {
    396 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
    397 	}
    398 
    399 	target, err := ap.ExtractObjectURI(followable)
    400 	if err != nil {
    401 		return nil, errors.New("error extracting object property from follow")
    402 	}
    403 	targetAccount, err := c.db.GetAccountByURI(ctx, target.String())
    404 	if err != nil {
    405 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
    406 	}
    407 
    408 	followRequest := &gtsmodel.FollowRequest{
    409 		URI:             uri,
    410 		AccountID:       originAccount.ID,
    411 		TargetAccountID: targetAccount.ID,
    412 	}
    413 
    414 	return followRequest, nil
    415 }
    416 
    417 func (c *converter) ASFollowToFollow(ctx context.Context, followable ap.Followable) (*gtsmodel.Follow, error) {
    418 	idProp := followable.GetJSONLDId()
    419 	if idProp == nil || !idProp.IsIRI() {
    420 		return nil, errors.New("no id property set on follow, or was not an iri")
    421 	}
    422 	uri := idProp.GetIRI().String()
    423 
    424 	origin, err := ap.ExtractActorURI(followable)
    425 	if err != nil {
    426 		return nil, errors.New("error extracting actor property from follow")
    427 	}
    428 	originAccount, err := c.db.GetAccountByURI(ctx, origin.String())
    429 	if err != nil {
    430 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
    431 	}
    432 
    433 	target, err := ap.ExtractObjectURI(followable)
    434 	if err != nil {
    435 		return nil, errors.New("error extracting object property from follow")
    436 	}
    437 	targetAccount, err := c.db.GetAccountByURI(ctx, target.String())
    438 	if err != nil {
    439 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
    440 	}
    441 
    442 	follow := &gtsmodel.Follow{
    443 		URI:             uri,
    444 		AccountID:       originAccount.ID,
    445 		TargetAccountID: targetAccount.ID,
    446 	}
    447 
    448 	return follow, nil
    449 }
    450 
    451 func (c *converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gtsmodel.StatusFave, error) {
    452 	idProp := likeable.GetJSONLDId()
    453 	if idProp == nil || !idProp.IsIRI() {
    454 		return nil, errors.New("no id property set on like, or was not an iri")
    455 	}
    456 	uri := idProp.GetIRI().String()
    457 
    458 	origin, err := ap.ExtractActorURI(likeable)
    459 	if err != nil {
    460 		return nil, errors.New("error extracting actor property from like")
    461 	}
    462 	originAccount, err := c.db.GetAccountByURI(ctx, origin.String())
    463 	if err != nil {
    464 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
    465 	}
    466 
    467 	target, err := ap.ExtractObjectURI(likeable)
    468 	if err != nil {
    469 		return nil, errors.New("error extracting object property from like")
    470 	}
    471 
    472 	targetStatus, err := c.db.GetStatusByURI(ctx, target.String())
    473 	if err != nil {
    474 		return nil, fmt.Errorf("error extracting status with uri %s from the database: %s", target.String(), err)
    475 	}
    476 
    477 	var targetAccount *gtsmodel.Account
    478 	if targetStatus.Account != nil {
    479 		targetAccount = targetStatus.Account
    480 	} else {
    481 		a, err := c.db.GetAccountByID(ctx, targetStatus.AccountID)
    482 		if err != nil {
    483 			return nil, fmt.Errorf("error extracting account with id %s from the database: %s", targetStatus.AccountID, err)
    484 		}
    485 		targetAccount = a
    486 	}
    487 
    488 	return &gtsmodel.StatusFave{
    489 		AccountID:       originAccount.ID,
    490 		Account:         originAccount,
    491 		TargetAccountID: targetAccount.ID,
    492 		TargetAccount:   targetAccount,
    493 		StatusID:        targetStatus.ID,
    494 		Status:          targetStatus,
    495 		URI:             uri,
    496 	}, nil
    497 }
    498 
    499 func (c *converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable) (*gtsmodel.Block, error) {
    500 	idProp := blockable.GetJSONLDId()
    501 	if idProp == nil || !idProp.IsIRI() {
    502 		return nil, errors.New("ASBlockToBlock: no id property set on block, or was not an iri")
    503 	}
    504 	uri := idProp.GetIRI().String()
    505 
    506 	origin, err := ap.ExtractActorURI(blockable)
    507 	if err != nil {
    508 		return nil, errors.New("ASBlockToBlock: error extracting actor property from block")
    509 	}
    510 	originAccount, err := c.db.GetAccountByURI(ctx, origin.String())
    511 	if err != nil {
    512 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
    513 	}
    514 
    515 	target, err := ap.ExtractObjectURI(blockable)
    516 	if err != nil {
    517 		return nil, errors.New("ASBlockToBlock: error extracting object property from block")
    518 	}
    519 
    520 	targetAccount, err := c.db.GetAccountByURI(ctx, target.String())
    521 	if err != nil {
    522 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
    523 	}
    524 
    525 	return &gtsmodel.Block{
    526 		AccountID:       originAccount.ID,
    527 		Account:         originAccount,
    528 		TargetAccountID: targetAccount.ID,
    529 		TargetAccount:   targetAccount,
    530 		URI:             uri,
    531 	}, nil
    532 }
    533 
    534 // Implementation note: this function creates and returns a boost WRAPPER
    535 // status which references the boosted status in its BoostOf field. No
    536 // dereferencing is done on the boosted status by this function. Callers
    537 // should look at `status.BoostOf` to see the status being boosted, and do
    538 // dereferencing on it as appropriate.
    539 //
    540 // The returned boolean indicates whether or not the boost has already been
    541 // seen before by this instance. If it was, then status.BoostOf should be a
    542 // fully filled-out status. If not, then only status.BoostOf.URI will be set.
    543 func (c *converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Announceable) (*gtsmodel.Status, bool, error) {
    544 	// Ensure item has an ID URI set.
    545 	_, statusURIStr, err := getURI(announceable)
    546 	if err != nil {
    547 		err = gtserror.Newf("error extracting URI: %w", err)
    548 		return nil, false, err
    549 	}
    550 
    551 	var (
    552 		status *gtsmodel.Status
    553 		isNew  bool
    554 	)
    555 
    556 	// Check if we already have this boost in the database.
    557 	status, err = c.db.GetStatusByURI(ctx, statusURIStr)
    558 	if err != nil && !errors.Is(err, db.ErrNoEntries) {
    559 		// Real database error.
    560 		err = gtserror.Newf("db error trying to get status with uri %s: %w", statusURIStr, err)
    561 		return nil, isNew, err
    562 	}
    563 
    564 	if status != nil {
    565 		// We already have this status,
    566 		// no need to proceed further.
    567 		return status, isNew, nil
    568 	}
    569 
    570 	// If we reach here, we're dealing
    571 	// with a boost we haven't seen before.
    572 	isNew = true
    573 
    574 	// Start assembling the new status
    575 	// (we already know the URI).
    576 	status = new(gtsmodel.Status)
    577 	status.URI = statusURIStr
    578 
    579 	// Get the URI of the boosted status.
    580 	boostOfURI, err := ap.ExtractObjectURI(announceable)
    581 	if err != nil {
    582 		err = gtserror.Newf("error extracting Object: %w", err)
    583 		return nil, isNew, err
    584 	}
    585 
    586 	// Set the URI of the boosted status on
    587 	// the new status, for later dereferencing.
    588 	boostOf := &gtsmodel.Status{
    589 		URI: boostOfURI.String(),
    590 	}
    591 	status.BoostOf = boostOf
    592 
    593 	// Extract published time for the boost.
    594 	published, err := ap.ExtractPublished(announceable)
    595 	if err != nil {
    596 		err = gtserror.Newf("error extracting published: %w", err)
    597 		return nil, isNew, err
    598 	}
    599 	status.CreatedAt = published
    600 	status.UpdatedAt = published
    601 
    602 	// Extract URI of the boosting account.
    603 	accountURI, err := ap.ExtractActorURI(announceable)
    604 	if err != nil {
    605 		err = gtserror.Newf("error extracting Actor: %w", err)
    606 		return nil, isNew, err
    607 	}
    608 	accountURIStr := accountURI.String()
    609 
    610 	// Try to get the boosting account based on the URI.
    611 	// This should have been dereferenced already before
    612 	// we hit this point so we can confidently error out
    613 	// if we don't have it.
    614 	account, err := c.db.GetAccountByURI(ctx, accountURIStr)
    615 	if err != nil {
    616 		err = gtserror.Newf("db error trying to get account with uri %s: %w", accountURIStr, err)
    617 		return nil, isNew, err
    618 	}
    619 	status.AccountID = account.ID
    620 	status.AccountURI = account.URI
    621 	status.Account = account
    622 
    623 	// Calculate intended visibility of the boost.
    624 	visibility, err := ap.ExtractVisibility(announceable, account.FollowersURI)
    625 	if err != nil {
    626 		err = gtserror.Newf("error extracting visibility: %w", err)
    627 		return nil, isNew, err
    628 	}
    629 	status.Visibility = visibility
    630 
    631 	// Below IDs will all be included in the
    632 	// boosted status, so set them empty here.
    633 	status.AttachmentIDs = make([]string, 0)
    634 	status.TagIDs = make([]string, 0)
    635 	status.MentionIDs = make([]string, 0)
    636 	status.EmojiIDs = make([]string, 0)
    637 
    638 	// Remaining fields on the boost status will be taken
    639 	// from the boosted status; it's not our job to do all
    640 	// that dereferencing here.
    641 	return status, isNew, nil
    642 }
    643 
    644 func (c *converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable) (*gtsmodel.Report, error) {
    645 	// Extract flag uri.
    646 	idProp := flaggable.GetJSONLDId()
    647 	if idProp == nil || !idProp.IsIRI() {
    648 		return nil, errors.New("ASFlagToReport: no id property set on flaggable, or was not an iri")
    649 	}
    650 	uri := idProp.GetIRI().String()
    651 
    652 	// Extract account that created the flag / report.
    653 	// This will usually be an instance actor.
    654 	actor, err := ap.ExtractActorURI(flaggable)
    655 	if err != nil {
    656 		return nil, fmt.Errorf("ASFlagToReport: error extracting actor: %w", err)
    657 	}
    658 	account, err := c.db.GetAccountByURI(ctx, actor.String())
    659 	if err != nil {
    660 		return nil, fmt.Errorf("ASFlagToReport: error in db fetching account with uri %s: %w", actor.String(), err)
    661 	}
    662 
    663 	// Get the content of the report.
    664 	// For Mastodon, this will just be a string, or nothing.
    665 	// In Misskey's case, it may also contain the URLs of
    666 	// one or more reported statuses, so extract these too.
    667 	content := ap.ExtractContent(flaggable)
    668 	statusURIs := []*url.URL{}
    669 	inlineURLs := misskeyReportInlineURLs(content)
    670 	statusURIs = append(statusURIs, inlineURLs...)
    671 
    672 	// Extract account and statuses targeted by the flag / report.
    673 	//
    674 	// Incoming flags from mastodon usually have a target account uri as
    675 	// first entry in objects, followed by URIs of one or more statuses.
    676 	// Misskey on the other hand will just contain the target account uri.
    677 	// We shouldn't assume the order of the objects will correspond to this,
    678 	// but we can check that he objects slice contains just one account, and
    679 	// maybe some statuses.
    680 	//
    681 	// Throw away anything that's not relevant to us.
    682 	objects, err := ap.ExtractObjectURIs(flaggable)
    683 	if err != nil {
    684 		return nil, fmt.Errorf("ASFlagToReport: error extracting objects: %w", err)
    685 	}
    686 	if len(objects) == 0 {
    687 		return nil, errors.New("ASFlagToReport: flaggable objects empty, can't create report")
    688 	}
    689 
    690 	var targetAccountURI *url.URL
    691 	for _, object := range objects {
    692 		switch {
    693 		case object.Host != config.GetHost():
    694 			// object doesn't belong to us, just ignore it
    695 			continue
    696 		case uris.IsUserPath(object):
    697 			if targetAccountURI != nil {
    698 				return nil, errors.New("ASFlagToReport: flaggable objects contained more than one target account uri")
    699 			}
    700 			targetAccountURI = object
    701 		case uris.IsStatusesPath(object):
    702 			statusURIs = append(statusURIs, object)
    703 		}
    704 	}
    705 
    706 	// Make sure we actually have a target account now.
    707 	if targetAccountURI == nil {
    708 		return nil, errors.New("ASFlagToReport: flaggable objects contained no recognizable target account uri")
    709 	}
    710 	targetAccount, err := c.db.GetAccountByURI(ctx, targetAccountURI.String())
    711 	if err != nil {
    712 		if errors.Is(err, db.ErrNoEntries) {
    713 			return nil, fmt.Errorf("ASFlagToReport: account with uri %s could not be found in the db", targetAccountURI.String())
    714 		}
    715 		return nil, fmt.Errorf("ASFlagToReport: db error getting account with uri %s: %w", targetAccountURI.String(), err)
    716 	}
    717 
    718 	// If we got some status URIs, try to get them from the db now
    719 	var (
    720 		statusIDs = make([]string, 0, len(statusURIs))
    721 		statuses  = make([]*gtsmodel.Status, 0, len(statusURIs))
    722 	)
    723 	for _, statusURI := range statusURIs {
    724 		statusURIString := statusURI.String()
    725 
    726 		// try getting this status by URI first, then URL
    727 		status, err := c.db.GetStatusByURI(ctx, statusURIString)
    728 		if err != nil {
    729 			if !errors.Is(err, db.ErrNoEntries) {
    730 				return nil, fmt.Errorf("ASFlagToReport: db error getting status with uri %s: %w", statusURIString, err)
    731 			}
    732 
    733 			status, err = c.db.GetStatusByURL(ctx, statusURIString)
    734 			if err != nil {
    735 				if !errors.Is(err, db.ErrNoEntries) {
    736 					return nil, fmt.Errorf("ASFlagToReport: db error getting status with url %s: %w", statusURIString, err)
    737 				}
    738 
    739 				log.Warnf(nil, "reported status %s could not be found in the db, skipping it", statusURIString)
    740 				continue
    741 			}
    742 		}
    743 
    744 		if status.AccountID != targetAccount.ID {
    745 			// status doesn't belong to this account, ignore it
    746 			continue
    747 		}
    748 
    749 		statusIDs = append(statusIDs, status.ID)
    750 		statuses = append(statuses, status)
    751 	}
    752 
    753 	// id etc should be handled the caller, so just return what we got
    754 	return &gtsmodel.Report{
    755 		URI:             uri,
    756 		AccountID:       account.ID,
    757 		Account:         account,
    758 		TargetAccountID: targetAccount.ID,
    759 		TargetAccount:   targetAccount,
    760 		Comment:         content,
    761 		StatusIDs:       statusIDs,
    762 		Statuses:        statuses,
    763 	}, nil
    764 }