mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 20:02:24 -05:00 
			
		
		
		
	start converting account to as person
This commit is contained in:
		
					parent
					
						
							
								ab49ebe4af
							
						
					
				
			
			
				commit
				
					
						dca484e00d
					
				
			
		
					 9 changed files with 789 additions and 525 deletions
				
			
		|  | @ -93,15 +93,15 @@ type Account struct { | ||||||
| 	// Last time this account was located using the webfinger API. | 	// Last time this account was located using the webfinger API. | ||||||
| 	LastWebfingeredAt time.Time `pg:"type:timestamp"` | 	LastWebfingeredAt time.Time `pg:"type:timestamp"` | ||||||
| 	// Address of this account's activitypub inbox, for sending activity to | 	// Address of this account's activitypub inbox, for sending activity to | ||||||
| 	InboxURL string `pg:",unique"` | 	InboxURI string `pg:",unique"` | ||||||
| 	// Address of this account's activitypub outbox | 	// Address of this account's activitypub outbox | ||||||
| 	OutboxURL string `pg:",unique"` | 	OutboxURI string `pg:",unique"` | ||||||
| 	// Don't support shared inbox right now so this is just a stub for a future implementation | 	// URI for getting the following list of this account | ||||||
| 	SharedInboxURL string `pg:",unique"` | 	FollowingURI string `pg:",unique"` | ||||||
| 	// URL for getting the followers list of this account | 	// URI for getting the followers list of this account | ||||||
| 	FollowersURL string `pg:",unique"` | 	FollowersURI string `pg:",unique"` | ||||||
| 	// URL for getting the featured collection list of this account | 	// URL for getting the featured collection list of this account | ||||||
| 	FeaturedCollectionURL string `pg:",unique"` | 	FeaturedCollectionURI string `pg:",unique"` | ||||||
| 	// What type of activitypub actor is this account? | 	// What type of activitypub actor is this account? | ||||||
| 	ActorType ActivityStreamsActor | 	ActorType ActivityStreamsActor | ||||||
| 	// This account is associated with x account id | 	// This account is associated with x account id | ||||||
|  |  | ||||||
|  | @ -468,10 +468,11 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr | ||||||
| 		PublicKeyURI:          newAccountURIs.PublicKeyURI, | 		PublicKeyURI:          newAccountURIs.PublicKeyURI, | ||||||
| 		ActorType:             gtsmodel.ActivityStreamsPerson, | 		ActorType:             gtsmodel.ActivityStreamsPerson, | ||||||
| 		URI:                   newAccountURIs.UserURI, | 		URI:                   newAccountURIs.UserURI, | ||||||
| 		InboxURL:              newAccountURIs.InboxURI, | 		InboxURI:              newAccountURIs.InboxURI, | ||||||
| 		OutboxURL:             newAccountURIs.OutboxURI, | 		OutboxURI:             newAccountURIs.OutboxURI, | ||||||
| 		FollowersURL:          newAccountURIs.FollowersURI, | 		FollowersURI:          newAccountURIs.FollowersURI, | ||||||
| 		FeaturedCollectionURL: newAccountURIs.CollectionURI, | 		FollowingURI:          newAccountURIs.FollowingURI, | ||||||
|  | 		FeaturedCollectionURI: newAccountURIs.CollectionURI, | ||||||
| 	} | 	} | ||||||
| 	if _, err = ps.conn.Model(a).Insert(); err != nil { | 	if _, err = ps.conn.Model(a).Insert(); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
|  | @ -144,7 +144,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() { | ||||||
| 			"owner": "%s", | 			"owner": "%s", | ||||||
| 			"publicKeyPem": "%s" | 			"publicKeyPem": "%s" | ||||||
| 		} | 		} | ||||||
| 	}`, sendingAccount.URI, sendingAccount.Username, sendingAccount.InboxURL, sendingAccount.PublicKeyURI, sendingAccount.URI, publicKeyString) | 	}`, sendingAccount.URI, sendingAccount.Username, sendingAccount.InboxURI, sendingAccount.PublicKeyURI, sendingAccount.URI, publicKeyString) | ||||||
| 
 | 
 | ||||||
| 	// create a transport controller whose client will just return the response body string we specified above | 	// create a transport controller whose client will just return the response body string we specified above | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) { | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||||
|  |  | ||||||
|  | @ -19,9 +19,7 @@ | ||||||
| package typeutils | package typeutils | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | ||||||
|  | @ -84,12 +82,18 @@ type TypeConverter interface { | ||||||
| 	MastoVisToVis(m mastotypes.Visibility) gtsmodel.Visibility | 	MastoVisToVis(m mastotypes.Visibility) gtsmodel.Visibility | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
| 		ACTIVITYPUB MODEL TO INTERNAL (gts) MODEL | 		ACTIVITYSTREAMS MODEL TO INTERNAL (gts) MODEL | ||||||
| 	*/ | 	*/ | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
| 		INTERNAL (gts) MODEL TO ACTIVITYPUB MODEL | 		INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL | ||||||
| 	*/ | 	*/ | ||||||
|  | 
 | ||||||
|  | 	// AccountToAS converts a gts model account into an activity streams person, suitable for federation | ||||||
|  | 	AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) | ||||||
|  | 
 | ||||||
|  | 	// StatusToAS converts a gts model status into an activity streams note, suitable for federation | ||||||
|  | 	StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type converter struct { | type converter struct { | ||||||
|  | @ -104,465 +108,3 @@ func NewConverter(config *config.Config, db db.DB) TypeConverter { | ||||||
| 		db:     db, | 		db:     db, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*mastotypes.Account, error) { |  | ||||||
| 	// we can build this sensitive account easily by first getting the public account.... |  | ||||||
| 	mastoAccount, err := c.AccountToMastoPublic(a) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// then adding the Source object to it... |  | ||||||
| 
 |  | ||||||
| 	// check pending follow requests aimed at this account |  | ||||||
| 	fr := []gtsmodel.FollowRequest{} |  | ||||||
| 	if err := c.db.GetFollowRequestsForAccountID(a.ID, &fr); err != nil { |  | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { |  | ||||||
| 			return nil, fmt.Errorf("error getting follow requests: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	var frc int |  | ||||||
| 	if fr != nil { |  | ||||||
| 		frc = len(fr) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mastoAccount.Source = &mastotypes.Source{ |  | ||||||
| 		Privacy:             c.VisToMasto(a.Privacy), |  | ||||||
| 		Sensitive:           a.Sensitive, |  | ||||||
| 		Language:            a.Language, |  | ||||||
| 		Note:                a.Note, |  | ||||||
| 		Fields:              mastoAccount.Fields, |  | ||||||
| 		FollowRequestsCount: frc, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return mastoAccount, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Account, error) { |  | ||||||
| 	// count followers |  | ||||||
| 	followers := []gtsmodel.Follow{} |  | ||||||
| 	if err := c.db.GetFollowersByAccountID(a.ID, &followers); err != nil { |  | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { |  | ||||||
| 			return nil, fmt.Errorf("error getting followers: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	var followersCount int |  | ||||||
| 	if followers != nil { |  | ||||||
| 		followersCount = len(followers) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// count following |  | ||||||
| 	following := []gtsmodel.Follow{} |  | ||||||
| 	if err := c.db.GetFollowingByAccountID(a.ID, &following); err != nil { |  | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { |  | ||||||
| 			return nil, fmt.Errorf("error getting following: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	var followingCount int |  | ||||||
| 	if following != nil { |  | ||||||
| 		followingCount = len(following) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// count statuses |  | ||||||
| 	statuses := []gtsmodel.Status{} |  | ||||||
| 	if err := c.db.GetStatusesByAccountID(a.ID, &statuses); err != nil { |  | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { |  | ||||||
| 			return nil, fmt.Errorf("error getting last statuses: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	var statusesCount int |  | ||||||
| 	if statuses != nil { |  | ||||||
| 		statusesCount = len(statuses) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// check when the last status was |  | ||||||
| 	lastStatus := >smodel.Status{} |  | ||||||
| 	if err := c.db.GetLastStatusForAccountID(a.ID, lastStatus); err != nil { |  | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { |  | ||||||
| 			return nil, fmt.Errorf("error getting last status: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	var lastStatusAt string |  | ||||||
| 	if lastStatus != nil { |  | ||||||
| 		lastStatusAt = lastStatus.CreatedAt.Format(time.RFC3339) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// build the avatar and header URLs |  | ||||||
| 	avi := >smodel.MediaAttachment{} |  | ||||||
| 	if err := c.db.GetAvatarForAccountID(avi, a.ID); err != nil { |  | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { |  | ||||||
| 			return nil, fmt.Errorf("error getting avatar: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	aviURL := avi.URL |  | ||||||
| 	aviURLStatic := avi.Thumbnail.URL |  | ||||||
| 
 |  | ||||||
| 	header := >smodel.MediaAttachment{} |  | ||||||
| 	if err := c.db.GetHeaderForAccountID(avi, a.ID); err != nil { |  | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { |  | ||||||
| 			return nil, fmt.Errorf("error getting header: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	headerURL := header.URL |  | ||||||
| 	headerURLStatic := header.Thumbnail.URL |  | ||||||
| 
 |  | ||||||
| 	// get the fields set on this account |  | ||||||
| 	fields := []mastotypes.Field{} |  | ||||||
| 	for _, f := range a.Fields { |  | ||||||
| 		mField := mastotypes.Field{ |  | ||||||
| 			Name:  f.Name, |  | ||||||
| 			Value: f.Value, |  | ||||||
| 		} |  | ||||||
| 		if !f.VerifiedAt.IsZero() { |  | ||||||
| 			mField.VerifiedAt = f.VerifiedAt.Format(time.RFC3339) |  | ||||||
| 		} |  | ||||||
| 		fields = append(fields, mField) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var acct string |  | ||||||
| 	if a.Domain != "" { |  | ||||||
| 		// this is a remote user |  | ||||||
| 		acct = fmt.Sprintf("%s@%s", a.Username, a.Domain) |  | ||||||
| 	} else { |  | ||||||
| 		// this is a local user |  | ||||||
| 		acct = a.Username |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &mastotypes.Account{ |  | ||||||
| 		ID:             a.ID, |  | ||||||
| 		Username:       a.Username, |  | ||||||
| 		Acct:           acct, |  | ||||||
| 		DisplayName:    a.DisplayName, |  | ||||||
| 		Locked:         a.Locked, |  | ||||||
| 		Bot:            a.Bot, |  | ||||||
| 		CreatedAt:      a.CreatedAt.Format(time.RFC3339), |  | ||||||
| 		Note:           a.Note, |  | ||||||
| 		URL:            a.URL, |  | ||||||
| 		Avatar:         aviURL, |  | ||||||
| 		AvatarStatic:   aviURLStatic, |  | ||||||
| 		Header:         headerURL, |  | ||||||
| 		HeaderStatic:   headerURLStatic, |  | ||||||
| 		FollowersCount: followersCount, |  | ||||||
| 		FollowingCount: followingCount, |  | ||||||
| 		StatusesCount:  statusesCount, |  | ||||||
| 		LastStatusAt:   lastStatusAt, |  | ||||||
| 		Emojis:         nil, // TODO: implement this |  | ||||||
| 		Fields:         fields, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) AppToMastoSensitive(a *gtsmodel.Application) (*mastotypes.Application, error) { |  | ||||||
| 	return &mastotypes.Application{ |  | ||||||
| 		ID:           a.ID, |  | ||||||
| 		Name:         a.Name, |  | ||||||
| 		Website:      a.Website, |  | ||||||
| 		RedirectURI:  a.RedirectURI, |  | ||||||
| 		ClientID:     a.ClientID, |  | ||||||
| 		ClientSecret: a.ClientSecret, |  | ||||||
| 		VapidKey:     a.VapidKey, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) AppToMastoPublic(a *gtsmodel.Application) (*mastotypes.Application, error) { |  | ||||||
| 	return &mastotypes.Application{ |  | ||||||
| 		Name:    a.Name, |  | ||||||
| 		Website: a.Website, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) AttachmentToMasto(a *gtsmodel.MediaAttachment) (mastotypes.Attachment, error) { |  | ||||||
| 	return mastotypes.Attachment{ |  | ||||||
| 		ID:               a.ID, |  | ||||||
| 		Type:             string(a.Type), |  | ||||||
| 		URL:              a.URL, |  | ||||||
| 		PreviewURL:       a.Thumbnail.URL, |  | ||||||
| 		RemoteURL:        a.RemoteURL, |  | ||||||
| 		PreviewRemoteURL: a.Thumbnail.RemoteURL, |  | ||||||
| 		Meta: mastotypes.MediaMeta{ |  | ||||||
| 			Original: mastotypes.MediaDimensions{ |  | ||||||
| 				Width:  a.FileMeta.Original.Width, |  | ||||||
| 				Height: a.FileMeta.Original.Height, |  | ||||||
| 				Size:   fmt.Sprintf("%dx%d", a.FileMeta.Original.Width, a.FileMeta.Original.Height), |  | ||||||
| 				Aspect: float32(a.FileMeta.Original.Aspect), |  | ||||||
| 			}, |  | ||||||
| 			Small: mastotypes.MediaDimensions{ |  | ||||||
| 				Width:  a.FileMeta.Small.Width, |  | ||||||
| 				Height: a.FileMeta.Small.Height, |  | ||||||
| 				Size:   fmt.Sprintf("%dx%d", a.FileMeta.Small.Width, a.FileMeta.Small.Height), |  | ||||||
| 				Aspect: float32(a.FileMeta.Small.Aspect), |  | ||||||
| 			}, |  | ||||||
| 			Focus: mastotypes.MediaFocus{ |  | ||||||
| 				X: a.FileMeta.Focus.X, |  | ||||||
| 				Y: a.FileMeta.Focus.Y, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		Description: a.Description, |  | ||||||
| 		Blurhash:    a.Blurhash, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error) { |  | ||||||
| 	target := >smodel.Account{} |  | ||||||
| 	if err := c.db.GetByID(m.TargetAccountID, target); err != nil { |  | ||||||
| 		return mastotypes.Mention{}, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var local bool |  | ||||||
| 	if target.Domain == "" { |  | ||||||
| 		local = true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var acct string |  | ||||||
| 	if local { |  | ||||||
| 		acct = fmt.Sprintf("@%s", target.Username) |  | ||||||
| 	} else { |  | ||||||
| 		acct = fmt.Sprintf("@%s@%s", target.Username, target.Domain) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return mastotypes.Mention{ |  | ||||||
| 		ID:       target.ID, |  | ||||||
| 		Username: target.Username, |  | ||||||
| 		URL:      target.URL, |  | ||||||
| 		Acct:     acct, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) EmojiToMasto(e *gtsmodel.Emoji) (mastotypes.Emoji, error) { |  | ||||||
| 	return mastotypes.Emoji{ |  | ||||||
| 		Shortcode:       e.Shortcode, |  | ||||||
| 		URL:             e.ImageURL, |  | ||||||
| 		StaticURL:       e.ImageStaticURL, |  | ||||||
| 		VisibleInPicker: e.VisibleInPicker, |  | ||||||
| 		Category:        e.CategoryID, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) TagToMasto(t *gtsmodel.Tag) (mastotypes.Tag, error) { |  | ||||||
| 	tagURL := fmt.Sprintf("%s://%s/tags/%s", c.config.Protocol, c.config.Host, t.Name) |  | ||||||
| 
 |  | ||||||
| 	return mastotypes.Tag{ |  | ||||||
| 		Name: t.Name, |  | ||||||
| 		URL:  tagURL, // we don't serve URLs with collections of tagged statuses (FOR NOW) so this is purely for mastodon compatibility ¯\_(ツ)_/¯ |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *converter) StatusToMasto( |  | ||||||
| 	s *gtsmodel.Status, |  | ||||||
| 	targetAccount *gtsmodel.Account, |  | ||||||
| 	requestingAccount *gtsmodel.Account, |  | ||||||
| 	boostOfAccount *gtsmodel.Account, |  | ||||||
| 	replyToAccount *gtsmodel.Account, |  | ||||||
| 	reblogOfStatus *gtsmodel.Status) (*mastotypes.Status, error) { |  | ||||||
| 
 |  | ||||||
| 	repliesCount, err := c.db.GetReplyCountForStatus(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error counting replies: %s", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	reblogsCount, err := c.db.GetReblogCountForStatus(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error counting reblogs: %s", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	favesCount, err := c.db.GetFaveCountForStatus(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error counting faves: %s", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var faved bool |  | ||||||
| 	var reblogged bool |  | ||||||
| 	var bookmarked bool |  | ||||||
| 	var pinned bool |  | ||||||
| 	var muted bool |  | ||||||
| 
 |  | ||||||
| 	// requestingAccount will be nil for public requests without auth |  | ||||||
| 	// But if it's not nil, we can also get information about the requestingAccount's interaction with this status |  | ||||||
| 	if requestingAccount != nil { |  | ||||||
| 		faved, err = c.db.StatusFavedBy(s, requestingAccount.ID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		reblogged, err = c.db.StatusRebloggedBy(s, requestingAccount.ID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		muted, err = c.db.StatusMutedBy(s, requestingAccount.ID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		bookmarked, err = c.db.StatusBookmarkedBy(s, requestingAccount.ID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		pinned, err = c.db.StatusPinnedBy(s, requestingAccount.ID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var mastoRebloggedStatus *mastotypes.Status // TODO |  | ||||||
| 
 |  | ||||||
| 	var mastoApplication *mastotypes.Application |  | ||||||
| 	if s.CreatedWithApplicationID != "" { |  | ||||||
| 		gtsApplication := >smodel.Application{} |  | ||||||
| 		if err := c.db.GetByID(s.CreatedWithApplicationID, gtsApplication); err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error fetching application used to create status: %s", err) |  | ||||||
| 		} |  | ||||||
| 		mastoApplication, err = c.AppToMastoPublic(gtsApplication) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("error parsing application used to create status: %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mastoTargetAccount, err := c.AccountToMastoPublic(targetAccount) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error parsing account of status author: %s", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mastoAttachments := []mastotypes.Attachment{} |  | ||||||
| 	// the status might already have some gts attachments on it if it's not been pulled directly from the database |  | ||||||
| 	// if so, we can directly convert the gts attachments into masto ones |  | ||||||
| 	if s.GTSMediaAttachments != nil { |  | ||||||
| 		for _, gtsAttachment := range s.GTSMediaAttachments { |  | ||||||
| 			mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting attachment with id %s: %s", gtsAttachment.ID, err) |  | ||||||
| 			} |  | ||||||
| 			mastoAttachments = append(mastoAttachments, mastoAttachment) |  | ||||||
| 		} |  | ||||||
| 		// the status doesn't have gts attachments on it, but it does have attachment IDs |  | ||||||
| 		// in this case, we need to pull the gts attachments from the db to convert them into masto ones |  | ||||||
| 	} else { |  | ||||||
| 		for _, a := range s.Attachments { |  | ||||||
| 			gtsAttachment := >smodel.MediaAttachment{} |  | ||||||
| 			if err := c.db.GetByID(a, gtsAttachment); err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error getting attachment with id %s: %s", a, err) |  | ||||||
| 			} |  | ||||||
| 			mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting attachment with id %s: %s", a, err) |  | ||||||
| 			} |  | ||||||
| 			mastoAttachments = append(mastoAttachments, mastoAttachment) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mastoMentions := []mastotypes.Mention{} |  | ||||||
| 	// the status might already have some gts mentions on it if it's not been pulled directly from the database |  | ||||||
| 	// if so, we can directly convert the gts mentions into masto ones |  | ||||||
| 	if s.GTSMentions != nil { |  | ||||||
| 		for _, gtsMention := range s.GTSMentions { |  | ||||||
| 			mastoMention, err := c.MentionToMasto(gtsMention) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) |  | ||||||
| 			} |  | ||||||
| 			mastoMentions = append(mastoMentions, mastoMention) |  | ||||||
| 		} |  | ||||||
| 		// the status doesn't have gts mentions on it, but it does have mention IDs |  | ||||||
| 		// in this case, we need to pull the gts mentions from the db to convert them into masto ones |  | ||||||
| 	} else { |  | ||||||
| 		for _, m := range s.Mentions { |  | ||||||
| 			gtsMention := >smodel.Mention{} |  | ||||||
| 			if err := c.db.GetByID(m, gtsMention); err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error getting mention with id %s: %s", m, err) |  | ||||||
| 			} |  | ||||||
| 			mastoMention, err := c.MentionToMasto(gtsMention) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) |  | ||||||
| 			} |  | ||||||
| 			mastoMentions = append(mastoMentions, mastoMention) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mastoTags := []mastotypes.Tag{} |  | ||||||
| 	// the status might already have some gts tags on it if it's not been pulled directly from the database |  | ||||||
| 	// if so, we can directly convert the gts tags into masto ones |  | ||||||
| 	if s.GTSTags != nil { |  | ||||||
| 		for _, gtsTag := range s.GTSTags { |  | ||||||
| 			mastoTag, err := c.TagToMasto(gtsTag) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) |  | ||||||
| 			} |  | ||||||
| 			mastoTags = append(mastoTags, mastoTag) |  | ||||||
| 		} |  | ||||||
| 		// the status doesn't have gts tags on it, but it does have tag IDs |  | ||||||
| 		// in this case, we need to pull the gts tags from the db to convert them into masto ones |  | ||||||
| 	} else { |  | ||||||
| 		for _, t := range s.Tags { |  | ||||||
| 			gtsTag := >smodel.Tag{} |  | ||||||
| 			if err := c.db.GetByID(t, gtsTag); err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error getting tag with id %s: %s", t, err) |  | ||||||
| 			} |  | ||||||
| 			mastoTag, err := c.TagToMasto(gtsTag) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) |  | ||||||
| 			} |  | ||||||
| 			mastoTags = append(mastoTags, mastoTag) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mastoEmojis := []mastotypes.Emoji{} |  | ||||||
| 	// the status might already have some gts emojis on it if it's not been pulled directly from the database |  | ||||||
| 	// if so, we can directly convert the gts emojis into masto ones |  | ||||||
| 	if s.GTSEmojis != nil { |  | ||||||
| 		for _, gtsEmoji := range s.GTSEmojis { |  | ||||||
| 			mastoEmoji, err := c.EmojiToMasto(gtsEmoji) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) |  | ||||||
| 			} |  | ||||||
| 			mastoEmojis = append(mastoEmojis, mastoEmoji) |  | ||||||
| 		} |  | ||||||
| 		// the status doesn't have gts emojis on it, but it does have emoji IDs |  | ||||||
| 		// in this case, we need to pull the gts emojis from the db to convert them into masto ones |  | ||||||
| 	} else { |  | ||||||
| 		for _, e := range s.Emojis { |  | ||||||
| 			gtsEmoji := >smodel.Emoji{} |  | ||||||
| 			if err := c.db.GetByID(e, gtsEmoji); err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error getting emoji with id %s: %s", e, err) |  | ||||||
| 			} |  | ||||||
| 			mastoEmoji, err := c.EmojiToMasto(gtsEmoji) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) |  | ||||||
| 			} |  | ||||||
| 			mastoEmojis = append(mastoEmojis, mastoEmoji) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var mastoCard *mastotypes.Card |  | ||||||
| 	var mastoPoll *mastotypes.Poll |  | ||||||
| 
 |  | ||||||
| 	return &mastotypes.Status{ |  | ||||||
| 		ID:                 s.ID, |  | ||||||
| 		CreatedAt:          s.CreatedAt.Format(time.RFC3339), |  | ||||||
| 		InReplyToID:        s.InReplyToID, |  | ||||||
| 		InReplyToAccountID: s.InReplyToAccountID, |  | ||||||
| 		Sensitive:          s.Sensitive, |  | ||||||
| 		SpoilerText:        s.ContentWarning, |  | ||||||
| 		Visibility:         c.VisToMasto(s.Visibility), |  | ||||||
| 		Language:           s.Language, |  | ||||||
| 		URI:                s.URI, |  | ||||||
| 		URL:                s.URL, |  | ||||||
| 		RepliesCount:       repliesCount, |  | ||||||
| 		ReblogsCount:       reblogsCount, |  | ||||||
| 		FavouritesCount:    favesCount, |  | ||||||
| 		Favourited:         faved, |  | ||||||
| 		Reblogged:          reblogged, |  | ||||||
| 		Muted:              muted, |  | ||||||
| 		Bookmarked:         bookmarked, |  | ||||||
| 		Pinned:             pinned, |  | ||||||
| 		Content:            s.Content, |  | ||||||
| 		Reblog:             mastoRebloggedStatus, |  | ||||||
| 		Application:        mastoApplication, |  | ||||||
| 		Account:            mastoTargetAccount, |  | ||||||
| 		MediaAttachments:   mastoAttachments, |  | ||||||
| 		Mentions:           mastoMentions, |  | ||||||
| 		Tags:               mastoTags, |  | ||||||
| 		Emojis:             mastoEmojis, |  | ||||||
| 		Card:               mastoCard, // TODO: implement cards |  | ||||||
| 		Poll:               mastoPoll, // TODO: implement polls |  | ||||||
| 		Text:               s.Text, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -37,18 +37,3 @@ func (c *converter) MastoVisToVis(m mastotypes.Visibility) gtsmodel.Visibility { | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // VisToMasto converts a gts visibility into its mastodon equivalent |  | ||||||
| func (c *converter) VisToMasto(m gtsmodel.Visibility) mastotypes.Visibility { |  | ||||||
| 	switch m { |  | ||||||
| 	case gtsmodel.VisibilityPublic: |  | ||||||
| 		return mastotypes.VisibilityPublic |  | ||||||
| 	case gtsmodel.VisibilityUnlocked: |  | ||||||
| 		return mastotypes.VisibilityUnlisted |  | ||||||
| 	case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly: |  | ||||||
| 		return mastotypes.VisibilityPrivate |  | ||||||
| 	case gtsmodel.VisibilityDirect: |  | ||||||
| 		return mastotypes.VisibilityDirect |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
							
								
								
									
										210
									
								
								internal/typeutils/internaltoas.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								internal/typeutils/internaltoas.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,210 @@ | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    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/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package typeutils | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/x509" | ||||||
|  | 	"encoding/pem" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/go-fed/activity/streams" | ||||||
|  | 	"github.com/go-fed/activity/streams/vocab" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Converts a gts model account into an Activity Streams person type, following | ||||||
|  | // the spec laid out for mastodon here: https://docs.joinmastodon.org/spec/activitypub/ | ||||||
|  | func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { | ||||||
|  | 	person := streams.NewActivityStreamsPerson() | ||||||
|  | 
 | ||||||
|  | 	// id should be the activitypub URI of this user | ||||||
|  | 	// something like https://example.org/users/example_user | ||||||
|  | 	profileIDURI, err := url.Parse(a.URI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	idProp := streams.NewJSONLDIdProperty() | ||||||
|  | 	idProp.SetIRI(profileIDURI) | ||||||
|  | 	person.SetJSONLDId(idProp) | ||||||
|  | 
 | ||||||
|  | 	// following | ||||||
|  | 	// The URI for retrieving a list of accounts this user is following | ||||||
|  | 	followingURI, err := url.Parse(a.FollowingURI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	followingProp := streams.NewActivityStreamsFollowingProperty() | ||||||
|  | 	followingProp.SetIRI(followingURI) | ||||||
|  | 	person.SetActivityStreamsFollowing(followingProp) | ||||||
|  | 
 | ||||||
|  | 	// followers | ||||||
|  | 	// The URI for retrieving a list of this user's followers | ||||||
|  | 	followersURI, err := url.Parse(a.FollowersURI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	followersProp := streams.NewActivityStreamsFollowersProperty() | ||||||
|  | 	followersProp.SetIRI(followersURI) | ||||||
|  | 	person.SetActivityStreamsFollowers(followersProp) | ||||||
|  | 
 | ||||||
|  | 	// inbox | ||||||
|  | 	// the activitypub inbox of this user for accepting messages | ||||||
|  | 	inboxURI, err := url.Parse(a.InboxURI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	inboxProp := streams.NewActivityStreamsInboxProperty() | ||||||
|  | 	inboxProp.SetIRI(inboxURI) | ||||||
|  | 	person.SetActivityStreamsInbox(inboxProp) | ||||||
|  | 
 | ||||||
|  | 	// outbox | ||||||
|  | 	// the activitypub outbox of this user for serving messages | ||||||
|  | 	outboxURI, err := url.Parse(a.OutboxURI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	outboxProp := streams.NewActivityStreamsOutboxProperty() | ||||||
|  | 	outboxProp.SetIRI(outboxURI) | ||||||
|  | 	person.SetActivityStreamsOutbox(outboxProp) | ||||||
|  | 
 | ||||||
|  | 	// featured posts | ||||||
|  | 	// Pinned posts. | ||||||
|  | 	featuredURI, err := url.Parse(a.FeaturedCollectionURI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	featuredProp := streams.NewTootFeaturedProperty() | ||||||
|  | 	featuredProp.SetIRI(featuredURI) | ||||||
|  | 	person.SetTootFeatured(featuredProp) | ||||||
|  | 
 | ||||||
|  | 	// featuredTags | ||||||
|  | 	// NOT IMPLEMENTED | ||||||
|  | 
 | ||||||
|  | 	// preferredUsername | ||||||
|  | 	// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI. | ||||||
|  | 	preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty() | ||||||
|  | 	preferredUsernameProp.SetXMLSchemaString(a.Username) | ||||||
|  | 	person.SetActivityStreamsPreferredUsername(preferredUsernameProp) | ||||||
|  | 
 | ||||||
|  | 	// name | ||||||
|  | 	// Used as profile display name. | ||||||
|  | 	nameProp := streams.NewActivityStreamsNameProperty() | ||||||
|  | 	if a.Username != "" { | ||||||
|  | 		nameProp.AppendXMLSchemaString(a.DisplayName) | ||||||
|  | 	} else { | ||||||
|  | 		nameProp.AppendXMLSchemaString(a.Username) | ||||||
|  | 	} | ||||||
|  | 	person.SetActivityStreamsName(nameProp) | ||||||
|  | 
 | ||||||
|  | 	// summary | ||||||
|  | 	// Used as profile bio. | ||||||
|  | 	if a.Note != "" { | ||||||
|  | 		summaryProp := streams.NewActivityStreamsSummaryProperty() | ||||||
|  | 		summaryProp.AppendXMLSchemaString(a.Note) | ||||||
|  | 		person.SetActivityStreamsSummary(summaryProp) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// url | ||||||
|  | 	// Used as profile link. | ||||||
|  | 	profileURL, err := url.Parse(a.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	urlProp := streams.NewActivityStreamsUrlProperty() | ||||||
|  | 	urlProp.AppendIRI(profileURL) | ||||||
|  | 
 | ||||||
|  | 	// manuallyApproveFollowers | ||||||
|  | 	// Will be shown as a locked account. | ||||||
|  | 	// TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool | ||||||
|  | 
 | ||||||
|  | 	// discoverable | ||||||
|  | 	// Will be shown in the profile directory. | ||||||
|  | 	discoverableProp := streams.NewTootDiscoverableProperty() | ||||||
|  | 	discoverableProp.Set(a.Discoverable) | ||||||
|  | 	person.SetTootDiscoverable(discoverableProp) | ||||||
|  | 
 | ||||||
|  | 	// devices | ||||||
|  | 	// NOT IMPLEMENTED | ||||||
|  | 
 | ||||||
|  | 	// alsoKnownAs | ||||||
|  | 	// Required for Move activity. | ||||||
|  | 	// TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool | ||||||
|  | 
 | ||||||
|  | 	// publicKey | ||||||
|  | 	// Required for signatures. | ||||||
|  | 	publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty() | ||||||
|  | 
 | ||||||
|  | 	// create the public key | ||||||
|  | 	publicKey := streams.NewW3IDSecurityV1PublicKey() | ||||||
|  | 
 | ||||||
|  | 	// set ID for the public key | ||||||
|  | 	publicKeyIDProp := streams.NewJSONLDIdProperty() | ||||||
|  | 	publicKeyURI, err := url.Parse(a.PublicKeyURI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	publicKeyIDProp.SetIRI(publicKeyURI) | ||||||
|  | 	publicKey.SetJSONLDId(publicKeyIDProp) | ||||||
|  | 
 | ||||||
|  | 	// set owner for the public key | ||||||
|  | 	publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty() | ||||||
|  | 	publicKeyOwnerProp.SetIRI(profileIDURI) | ||||||
|  | 	publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp) | ||||||
|  | 
 | ||||||
|  | 	// set the pem key itself | ||||||
|  | 	encodedPublicKey, err := x509.MarshalPKIXPublicKey(a.PublicKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	publicKeyBytes := pem.EncodeToMemory(&pem.Block{ | ||||||
|  | 		Type:  "PUBLIC KEY", | ||||||
|  | 		Bytes: encodedPublicKey, | ||||||
|  | 	}) | ||||||
|  | 	publicKeyString := strings.ReplaceAll(string(publicKeyBytes), "\n", "\\n") // replace all the newlines with backslash n | ||||||
|  | 	publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty() | ||||||
|  | 	publicKeyPEMProp.Set(publicKeyString) | ||||||
|  | 
 | ||||||
|  | 	// append the public key to the public key property | ||||||
|  | 	publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey) | ||||||
|  | 
 | ||||||
|  | 	// set the public key property on the Person | ||||||
|  | 	person.SetW3IDSecurityV1PublicKey(publicKeyProp) | ||||||
|  | 
 | ||||||
|  | 	// tag | ||||||
|  | 	// Any tags used in the summary of this profile | ||||||
|  | 
 | ||||||
|  | 	// attachment | ||||||
|  | 	// Used for profile fields. | ||||||
|  | 
 | ||||||
|  | 	// endpoints | ||||||
|  | 	// NOT IMPLEMENTED -- this is for shared inbox which we don't use | ||||||
|  | 
 | ||||||
|  | 	// icon | ||||||
|  | 	// Used as profile avatar. | ||||||
|  | 
 | ||||||
|  | 	// image | ||||||
|  | 	// Used as profile header. | ||||||
|  | 
 | ||||||
|  | 	return person, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) { | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								internal/typeutils/internaltoas_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								internal/typeutils/internaltoas_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    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/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package typeutils_test | ||||||
							
								
								
									
										505
									
								
								internal/typeutils/internaltofrontend.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										505
									
								
								internal/typeutils/internaltofrontend.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,505 @@ | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    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/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package typeutils | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/mastotypes" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*mastotypes.Account, error) { | ||||||
|  | 	// we can build this sensitive account easily by first getting the public account.... | ||||||
|  | 	mastoAccount, err := c.AccountToMastoPublic(a) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// then adding the Source object to it... | ||||||
|  | 
 | ||||||
|  | 	// check pending follow requests aimed at this account | ||||||
|  | 	fr := []gtsmodel.FollowRequest{} | ||||||
|  | 	if err := c.db.GetFollowRequestsForAccountID(a.ID, &fr); err != nil { | ||||||
|  | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | 			return nil, fmt.Errorf("error getting follow requests: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	var frc int | ||||||
|  | 	if fr != nil { | ||||||
|  | 		frc = len(fr) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mastoAccount.Source = &mastotypes.Source{ | ||||||
|  | 		Privacy:             c.VisToMasto(a.Privacy), | ||||||
|  | 		Sensitive:           a.Sensitive, | ||||||
|  | 		Language:            a.Language, | ||||||
|  | 		Note:                a.Note, | ||||||
|  | 		Fields:              mastoAccount.Fields, | ||||||
|  | 		FollowRequestsCount: frc, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return mastoAccount, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Account, error) { | ||||||
|  | 	// count followers | ||||||
|  | 	followers := []gtsmodel.Follow{} | ||||||
|  | 	if err := c.db.GetFollowersByAccountID(a.ID, &followers); err != nil { | ||||||
|  | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | 			return nil, fmt.Errorf("error getting followers: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	var followersCount int | ||||||
|  | 	if followers != nil { | ||||||
|  | 		followersCount = len(followers) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// count following | ||||||
|  | 	following := []gtsmodel.Follow{} | ||||||
|  | 	if err := c.db.GetFollowingByAccountID(a.ID, &following); err != nil { | ||||||
|  | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | 			return nil, fmt.Errorf("error getting following: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	var followingCount int | ||||||
|  | 	if following != nil { | ||||||
|  | 		followingCount = len(following) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// count statuses | ||||||
|  | 	statuses := []gtsmodel.Status{} | ||||||
|  | 	if err := c.db.GetStatusesByAccountID(a.ID, &statuses); err != nil { | ||||||
|  | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | 			return nil, fmt.Errorf("error getting last statuses: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	var statusesCount int | ||||||
|  | 	if statuses != nil { | ||||||
|  | 		statusesCount = len(statuses) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// check when the last status was | ||||||
|  | 	lastStatus := >smodel.Status{} | ||||||
|  | 	if err := c.db.GetLastStatusForAccountID(a.ID, lastStatus); err != nil { | ||||||
|  | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | 			return nil, fmt.Errorf("error getting last status: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	var lastStatusAt string | ||||||
|  | 	if lastStatus != nil { | ||||||
|  | 		lastStatusAt = lastStatus.CreatedAt.Format(time.RFC3339) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// build the avatar and header URLs | ||||||
|  | 	avi := >smodel.MediaAttachment{} | ||||||
|  | 	if err := c.db.GetAvatarForAccountID(avi, a.ID); err != nil { | ||||||
|  | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | 			return nil, fmt.Errorf("error getting avatar: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	aviURL := avi.URL | ||||||
|  | 	aviURLStatic := avi.Thumbnail.URL | ||||||
|  | 
 | ||||||
|  | 	header := >smodel.MediaAttachment{} | ||||||
|  | 	if err := c.db.GetHeaderForAccountID(avi, a.ID); err != nil { | ||||||
|  | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | 			return nil, fmt.Errorf("error getting header: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	headerURL := header.URL | ||||||
|  | 	headerURLStatic := header.Thumbnail.URL | ||||||
|  | 
 | ||||||
|  | 	// get the fields set on this account | ||||||
|  | 	fields := []mastotypes.Field{} | ||||||
|  | 	for _, f := range a.Fields { | ||||||
|  | 		mField := mastotypes.Field{ | ||||||
|  | 			Name:  f.Name, | ||||||
|  | 			Value: f.Value, | ||||||
|  | 		} | ||||||
|  | 		if !f.VerifiedAt.IsZero() { | ||||||
|  | 			mField.VerifiedAt = f.VerifiedAt.Format(time.RFC3339) | ||||||
|  | 		} | ||||||
|  | 		fields = append(fields, mField) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var acct string | ||||||
|  | 	if a.Domain != "" { | ||||||
|  | 		// this is a remote user | ||||||
|  | 		acct = fmt.Sprintf("%s@%s", a.Username, a.Domain) | ||||||
|  | 	} else { | ||||||
|  | 		// this is a local user | ||||||
|  | 		acct = a.Username | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &mastotypes.Account{ | ||||||
|  | 		ID:             a.ID, | ||||||
|  | 		Username:       a.Username, | ||||||
|  | 		Acct:           acct, | ||||||
|  | 		DisplayName:    a.DisplayName, | ||||||
|  | 		Locked:         a.Locked, | ||||||
|  | 		Bot:            a.Bot, | ||||||
|  | 		CreatedAt:      a.CreatedAt.Format(time.RFC3339), | ||||||
|  | 		Note:           a.Note, | ||||||
|  | 		URL:            a.URL, | ||||||
|  | 		Avatar:         aviURL, | ||||||
|  | 		AvatarStatic:   aviURLStatic, | ||||||
|  | 		Header:         headerURL, | ||||||
|  | 		HeaderStatic:   headerURLStatic, | ||||||
|  | 		FollowersCount: followersCount, | ||||||
|  | 		FollowingCount: followingCount, | ||||||
|  | 		StatusesCount:  statusesCount, | ||||||
|  | 		LastStatusAt:   lastStatusAt, | ||||||
|  | 		Emojis:         nil, // TODO: implement this | ||||||
|  | 		Fields:         fields, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) AppToMastoSensitive(a *gtsmodel.Application) (*mastotypes.Application, error) { | ||||||
|  | 	return &mastotypes.Application{ | ||||||
|  | 		ID:           a.ID, | ||||||
|  | 		Name:         a.Name, | ||||||
|  | 		Website:      a.Website, | ||||||
|  | 		RedirectURI:  a.RedirectURI, | ||||||
|  | 		ClientID:     a.ClientID, | ||||||
|  | 		ClientSecret: a.ClientSecret, | ||||||
|  | 		VapidKey:     a.VapidKey, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) AppToMastoPublic(a *gtsmodel.Application) (*mastotypes.Application, error) { | ||||||
|  | 	return &mastotypes.Application{ | ||||||
|  | 		Name:    a.Name, | ||||||
|  | 		Website: a.Website, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) AttachmentToMasto(a *gtsmodel.MediaAttachment) (mastotypes.Attachment, error) { | ||||||
|  | 	return mastotypes.Attachment{ | ||||||
|  | 		ID:               a.ID, | ||||||
|  | 		Type:             string(a.Type), | ||||||
|  | 		URL:              a.URL, | ||||||
|  | 		PreviewURL:       a.Thumbnail.URL, | ||||||
|  | 		RemoteURL:        a.RemoteURL, | ||||||
|  | 		PreviewRemoteURL: a.Thumbnail.RemoteURL, | ||||||
|  | 		Meta: mastotypes.MediaMeta{ | ||||||
|  | 			Original: mastotypes.MediaDimensions{ | ||||||
|  | 				Width:  a.FileMeta.Original.Width, | ||||||
|  | 				Height: a.FileMeta.Original.Height, | ||||||
|  | 				Size:   fmt.Sprintf("%dx%d", a.FileMeta.Original.Width, a.FileMeta.Original.Height), | ||||||
|  | 				Aspect: float32(a.FileMeta.Original.Aspect), | ||||||
|  | 			}, | ||||||
|  | 			Small: mastotypes.MediaDimensions{ | ||||||
|  | 				Width:  a.FileMeta.Small.Width, | ||||||
|  | 				Height: a.FileMeta.Small.Height, | ||||||
|  | 				Size:   fmt.Sprintf("%dx%d", a.FileMeta.Small.Width, a.FileMeta.Small.Height), | ||||||
|  | 				Aspect: float32(a.FileMeta.Small.Aspect), | ||||||
|  | 			}, | ||||||
|  | 			Focus: mastotypes.MediaFocus{ | ||||||
|  | 				X: a.FileMeta.Focus.X, | ||||||
|  | 				Y: a.FileMeta.Focus.Y, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Description: a.Description, | ||||||
|  | 		Blurhash:    a.Blurhash, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error) { | ||||||
|  | 	target := >smodel.Account{} | ||||||
|  | 	if err := c.db.GetByID(m.TargetAccountID, target); err != nil { | ||||||
|  | 		return mastotypes.Mention{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var local bool | ||||||
|  | 	if target.Domain == "" { | ||||||
|  | 		local = true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var acct string | ||||||
|  | 	if local { | ||||||
|  | 		acct = fmt.Sprintf("@%s", target.Username) | ||||||
|  | 	} else { | ||||||
|  | 		acct = fmt.Sprintf("@%s@%s", target.Username, target.Domain) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return mastotypes.Mention{ | ||||||
|  | 		ID:       target.ID, | ||||||
|  | 		Username: target.Username, | ||||||
|  | 		URL:      target.URL, | ||||||
|  | 		Acct:     acct, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) EmojiToMasto(e *gtsmodel.Emoji) (mastotypes.Emoji, error) { | ||||||
|  | 	return mastotypes.Emoji{ | ||||||
|  | 		Shortcode:       e.Shortcode, | ||||||
|  | 		URL:             e.ImageURL, | ||||||
|  | 		StaticURL:       e.ImageStaticURL, | ||||||
|  | 		VisibleInPicker: e.VisibleInPicker, | ||||||
|  | 		Category:        e.CategoryID, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) TagToMasto(t *gtsmodel.Tag) (mastotypes.Tag, error) { | ||||||
|  | 	tagURL := fmt.Sprintf("%s://%s/tags/%s", c.config.Protocol, c.config.Host, t.Name) | ||||||
|  | 
 | ||||||
|  | 	return mastotypes.Tag{ | ||||||
|  | 		Name: t.Name, | ||||||
|  | 		URL:  tagURL, // we don't serve URLs with collections of tagged statuses (FOR NOW) so this is purely for mastodon compatibility ¯\_(ツ)_/¯ | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *converter) StatusToMasto( | ||||||
|  | 	s *gtsmodel.Status, | ||||||
|  | 	targetAccount *gtsmodel.Account, | ||||||
|  | 	requestingAccount *gtsmodel.Account, | ||||||
|  | 	boostOfAccount *gtsmodel.Account, | ||||||
|  | 	replyToAccount *gtsmodel.Account, | ||||||
|  | 	reblogOfStatus *gtsmodel.Status) (*mastotypes.Status, error) { | ||||||
|  | 
 | ||||||
|  | 	repliesCount, err := c.db.GetReplyCountForStatus(s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error counting replies: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	reblogsCount, err := c.db.GetReblogCountForStatus(s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error counting reblogs: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	favesCount, err := c.db.GetFaveCountForStatus(s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error counting faves: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var faved bool | ||||||
|  | 	var reblogged bool | ||||||
|  | 	var bookmarked bool | ||||||
|  | 	var pinned bool | ||||||
|  | 	var muted bool | ||||||
|  | 
 | ||||||
|  | 	// requestingAccount will be nil for public requests without auth | ||||||
|  | 	// But if it's not nil, we can also get information about the requestingAccount's interaction with this status | ||||||
|  | 	if requestingAccount != nil { | ||||||
|  | 		faved, err = c.db.StatusFavedBy(s, requestingAccount.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		reblogged, err = c.db.StatusRebloggedBy(s, requestingAccount.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		muted, err = c.db.StatusMutedBy(s, requestingAccount.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		bookmarked, err = c.db.StatusBookmarkedBy(s, requestingAccount.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		pinned, err = c.db.StatusPinnedBy(s, requestingAccount.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var mastoRebloggedStatus *mastotypes.Status // TODO | ||||||
|  | 
 | ||||||
|  | 	var mastoApplication *mastotypes.Application | ||||||
|  | 	if s.CreatedWithApplicationID != "" { | ||||||
|  | 		gtsApplication := >smodel.Application{} | ||||||
|  | 		if err := c.db.GetByID(s.CreatedWithApplicationID, gtsApplication); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error fetching application used to create status: %s", err) | ||||||
|  | 		} | ||||||
|  | 		mastoApplication, err = c.AppToMastoPublic(gtsApplication) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error parsing application used to create status: %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mastoTargetAccount, err := c.AccountToMastoPublic(targetAccount) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error parsing account of status author: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mastoAttachments := []mastotypes.Attachment{} | ||||||
|  | 	// the status might already have some gts attachments on it if it's not been pulled directly from the database | ||||||
|  | 	// if so, we can directly convert the gts attachments into masto ones | ||||||
|  | 	if s.GTSMediaAttachments != nil { | ||||||
|  | 		for _, gtsAttachment := range s.GTSMediaAttachments { | ||||||
|  | 			mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting attachment with id %s: %s", gtsAttachment.ID, err) | ||||||
|  | 			} | ||||||
|  | 			mastoAttachments = append(mastoAttachments, mastoAttachment) | ||||||
|  | 		} | ||||||
|  | 		// the status doesn't have gts attachments on it, but it does have attachment IDs | ||||||
|  | 		// in this case, we need to pull the gts attachments from the db to convert them into masto ones | ||||||
|  | 	} else { | ||||||
|  | 		for _, a := range s.Attachments { | ||||||
|  | 			gtsAttachment := >smodel.MediaAttachment{} | ||||||
|  | 			if err := c.db.GetByID(a, gtsAttachment); err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error getting attachment with id %s: %s", a, err) | ||||||
|  | 			} | ||||||
|  | 			mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting attachment with id %s: %s", a, err) | ||||||
|  | 			} | ||||||
|  | 			mastoAttachments = append(mastoAttachments, mastoAttachment) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mastoMentions := []mastotypes.Mention{} | ||||||
|  | 	// the status might already have some gts mentions on it if it's not been pulled directly from the database | ||||||
|  | 	// if so, we can directly convert the gts mentions into masto ones | ||||||
|  | 	if s.GTSMentions != nil { | ||||||
|  | 		for _, gtsMention := range s.GTSMentions { | ||||||
|  | 			mastoMention, err := c.MentionToMasto(gtsMention) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) | ||||||
|  | 			} | ||||||
|  | 			mastoMentions = append(mastoMentions, mastoMention) | ||||||
|  | 		} | ||||||
|  | 		// the status doesn't have gts mentions on it, but it does have mention IDs | ||||||
|  | 		// in this case, we need to pull the gts mentions from the db to convert them into masto ones | ||||||
|  | 	} else { | ||||||
|  | 		for _, m := range s.Mentions { | ||||||
|  | 			gtsMention := >smodel.Mention{} | ||||||
|  | 			if err := c.db.GetByID(m, gtsMention); err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error getting mention with id %s: %s", m, err) | ||||||
|  | 			} | ||||||
|  | 			mastoMention, err := c.MentionToMasto(gtsMention) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) | ||||||
|  | 			} | ||||||
|  | 			mastoMentions = append(mastoMentions, mastoMention) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mastoTags := []mastotypes.Tag{} | ||||||
|  | 	// the status might already have some gts tags on it if it's not been pulled directly from the database | ||||||
|  | 	// if so, we can directly convert the gts tags into masto ones | ||||||
|  | 	if s.GTSTags != nil { | ||||||
|  | 		for _, gtsTag := range s.GTSTags { | ||||||
|  | 			mastoTag, err := c.TagToMasto(gtsTag) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) | ||||||
|  | 			} | ||||||
|  | 			mastoTags = append(mastoTags, mastoTag) | ||||||
|  | 		} | ||||||
|  | 		// the status doesn't have gts tags on it, but it does have tag IDs | ||||||
|  | 		// in this case, we need to pull the gts tags from the db to convert them into masto ones | ||||||
|  | 	} else { | ||||||
|  | 		for _, t := range s.Tags { | ||||||
|  | 			gtsTag := >smodel.Tag{} | ||||||
|  | 			if err := c.db.GetByID(t, gtsTag); err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error getting tag with id %s: %s", t, err) | ||||||
|  | 			} | ||||||
|  | 			mastoTag, err := c.TagToMasto(gtsTag) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) | ||||||
|  | 			} | ||||||
|  | 			mastoTags = append(mastoTags, mastoTag) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mastoEmojis := []mastotypes.Emoji{} | ||||||
|  | 	// the status might already have some gts emojis on it if it's not been pulled directly from the database | ||||||
|  | 	// if so, we can directly convert the gts emojis into masto ones | ||||||
|  | 	if s.GTSEmojis != nil { | ||||||
|  | 		for _, gtsEmoji := range s.GTSEmojis { | ||||||
|  | 			mastoEmoji, err := c.EmojiToMasto(gtsEmoji) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) | ||||||
|  | 			} | ||||||
|  | 			mastoEmojis = append(mastoEmojis, mastoEmoji) | ||||||
|  | 		} | ||||||
|  | 		// the status doesn't have gts emojis on it, but it does have emoji IDs | ||||||
|  | 		// in this case, we need to pull the gts emojis from the db to convert them into masto ones | ||||||
|  | 	} else { | ||||||
|  | 		for _, e := range s.Emojis { | ||||||
|  | 			gtsEmoji := >smodel.Emoji{} | ||||||
|  | 			if err := c.db.GetByID(e, gtsEmoji); err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error getting emoji with id %s: %s", e, err) | ||||||
|  | 			} | ||||||
|  | 			mastoEmoji, err := c.EmojiToMasto(gtsEmoji) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) | ||||||
|  | 			} | ||||||
|  | 			mastoEmojis = append(mastoEmojis, mastoEmoji) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var mastoCard *mastotypes.Card | ||||||
|  | 	var mastoPoll *mastotypes.Poll | ||||||
|  | 
 | ||||||
|  | 	return &mastotypes.Status{ | ||||||
|  | 		ID:                 s.ID, | ||||||
|  | 		CreatedAt:          s.CreatedAt.Format(time.RFC3339), | ||||||
|  | 		InReplyToID:        s.InReplyToID, | ||||||
|  | 		InReplyToAccountID: s.InReplyToAccountID, | ||||||
|  | 		Sensitive:          s.Sensitive, | ||||||
|  | 		SpoilerText:        s.ContentWarning, | ||||||
|  | 		Visibility:         c.VisToMasto(s.Visibility), | ||||||
|  | 		Language:           s.Language, | ||||||
|  | 		URI:                s.URI, | ||||||
|  | 		URL:                s.URL, | ||||||
|  | 		RepliesCount:       repliesCount, | ||||||
|  | 		ReblogsCount:       reblogsCount, | ||||||
|  | 		FavouritesCount:    favesCount, | ||||||
|  | 		Favourited:         faved, | ||||||
|  | 		Reblogged:          reblogged, | ||||||
|  | 		Muted:              muted, | ||||||
|  | 		Bookmarked:         bookmarked, | ||||||
|  | 		Pinned:             pinned, | ||||||
|  | 		Content:            s.Content, | ||||||
|  | 		Reblog:             mastoRebloggedStatus, | ||||||
|  | 		Application:        mastoApplication, | ||||||
|  | 		Account:            mastoTargetAccount, | ||||||
|  | 		MediaAttachments:   mastoAttachments, | ||||||
|  | 		Mentions:           mastoMentions, | ||||||
|  | 		Tags:               mastoTags, | ||||||
|  | 		Emojis:             mastoEmojis, | ||||||
|  | 		Card:               mastoCard, // TODO: implement cards | ||||||
|  | 		Poll:               mastoPoll, // TODO: implement polls | ||||||
|  | 		Text:               s.Text, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // VisToMasto converts a gts visibility into its mastodon equivalent | ||||||
|  | func (c *converter) VisToMasto(m gtsmodel.Visibility) mastotypes.Visibility { | ||||||
|  | 	switch m { | ||||||
|  | 	case gtsmodel.VisibilityPublic: | ||||||
|  | 		return mastotypes.VisibilityPublic | ||||||
|  | 	case gtsmodel.VisibilityUnlocked: | ||||||
|  | 		return mastotypes.VisibilityUnlisted | ||||||
|  | 	case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly: | ||||||
|  | 		return mastotypes.VisibilityPrivate | ||||||
|  | 	case gtsmodel.VisibilityDirect: | ||||||
|  | 		return mastotypes.VisibilityDirect | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | @ -284,16 +284,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { | ||||||
| 			URI:                     "http://localhost:8080/users/weed_lord420", | 			URI:                     "http://localhost:8080/users/weed_lord420", | ||||||
| 			URL:                     "http://localhost:8080/@weed_lord420", | 			URL:                     "http://localhost:8080/@weed_lord420", | ||||||
| 			LastWebfingeredAt:       time.Time{}, | 			LastWebfingeredAt:       time.Time{}, | ||||||
| 			InboxURL:                "http://localhost:8080/users/weed_lord420/inbox", | 			InboxURI:                "http://localhost:8080/users/weed_lord420/inbox", | ||||||
| 			OutboxURL:               "http://localhost:8080/users/weed_lord420/outbox", | 			OutboxURI:               "http://localhost:8080/users/weed_lord420/outbox", | ||||||
| 			SharedInboxURL:          "", | 			FollowersURI:            "http://localhost:8080/users/weed_lord420/followers", | ||||||
| 			FollowersURL:            "http://localhost:8080/users/weed_lord420/followers", | 			FollowingURI:            "http://localhost:8080/users/weed_lord420/following", | ||||||
| 			FeaturedCollectionURL:   "http://localhost:8080/users/weed_lord420/collections/featured", | 			FeaturedCollectionURI:   "http://localhost:8080/users/weed_lord420/collections/featured", | ||||||
| 			ActorType:               gtsmodel.ActivityStreamsPerson, | 			ActorType:               gtsmodel.ActivityStreamsPerson, | ||||||
| 			AlsoKnownAs:             "", | 			AlsoKnownAs:             "", | ||||||
| 			PrivateKey:              &rsa.PrivateKey{}, | 			PrivateKey:              &rsa.PrivateKey{}, | ||||||
| 			PublicKey:               &rsa.PublicKey{}, | 			PublicKey:               &rsa.PublicKey{}, | ||||||
| 			PublicKeyURI:            "http://localhost:8080/users/weed_lord420/publickey", | 			PublicKeyURI:            "http://localhost:8080/users/weed_lord420#main-key", | ||||||
| 			SensitizedAt:            time.Time{}, | 			SensitizedAt:            time.Time{}, | ||||||
| 			SilencedAt:              time.Time{}, | 			SilencedAt:              time.Time{}, | ||||||
| 			SuspendedAt:             time.Time{}, | 			SuspendedAt:             time.Time{}, | ||||||
|  | @ -321,13 +321,13 @@ func NewTestAccounts() map[string]*gtsmodel.Account { | ||||||
| 			Language:                "en", | 			Language:                "en", | ||||||
| 			URI:                     "http://localhost:8080/users/admin", | 			URI:                     "http://localhost:8080/users/admin", | ||||||
| 			URL:                     "http://localhost:8080/@admin", | 			URL:                     "http://localhost:8080/@admin", | ||||||
| 			PublicKeyURI:            "http://localhost:8080/users/admin/publickey", | 			PublicKeyURI:            "http://localhost:8080/users/admin#main-key", | ||||||
| 			LastWebfingeredAt:       time.Time{}, | 			LastWebfingeredAt:       time.Time{}, | ||||||
| 			InboxURL:                "http://localhost:8080/users/admin/inbox", | 			InboxURI:                "http://localhost:8080/users/admin/inbox", | ||||||
| 			OutboxURL:               "http://localhost:8080/users/admin/outbox", | 			OutboxURI:               "http://localhost:8080/users/admin/outbox", | ||||||
| 			SharedInboxURL:          "", | 			FollowersURI:            "http://localhost:8080/users/admin/followers", | ||||||
| 			FollowersURL:            "http://localhost:8080/users/admin/followers", | 			FollowingURI:            "http://localhost:8080/users/admin/following", | ||||||
| 			FeaturedCollectionURL:   "http://localhost:8080/users/admin/collections/featured", | 			FeaturedCollectionURI:   "http://localhost:8080/users/admin/collections/featured", | ||||||
| 			ActorType:               gtsmodel.ActivityStreamsPerson, | 			ActorType:               gtsmodel.ActivityStreamsPerson, | ||||||
| 			AlsoKnownAs:             "", | 			AlsoKnownAs:             "", | ||||||
| 			PrivateKey:              &rsa.PrivateKey{}, | 			PrivateKey:              &rsa.PrivateKey{}, | ||||||
|  | @ -360,16 +360,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { | ||||||
| 			URI:                     "http://localhost:8080/users/the_mighty_zork", | 			URI:                     "http://localhost:8080/users/the_mighty_zork", | ||||||
| 			URL:                     "http://localhost:8080/@the_mighty_zork", | 			URL:                     "http://localhost:8080/@the_mighty_zork", | ||||||
| 			LastWebfingeredAt:       time.Time{}, | 			LastWebfingeredAt:       time.Time{}, | ||||||
| 			InboxURL:                "http://localhost:8080/users/the_mighty_zork/inbox", | 			InboxURI:                "http://localhost:8080/users/the_mighty_zork/inbox", | ||||||
| 			OutboxURL:               "http://localhost:8080/users/the_mighty_zork/outbox", | 			OutboxURI:               "http://localhost:8080/users/the_mighty_zork/outbox", | ||||||
| 			SharedInboxURL:          "", | 			FollowersURI:            "http://localhost:8080/users/the_mighty_zork/followers", | ||||||
| 			FollowersURL:            "http://localhost:8080/users/the_mighty_zork/followers", | 			FollowingURI:            "http://localhost:8080/users/the_mighty_zork/following", | ||||||
| 			FeaturedCollectionURL:   "http://localhost:8080/users/the_mighty_zork/collections/featured", | 			FeaturedCollectionURI:   "http://localhost:8080/users/the_mighty_zork/collections/featured", | ||||||
| 			ActorType:               gtsmodel.ActivityStreamsPerson, | 			ActorType:               gtsmodel.ActivityStreamsPerson, | ||||||
| 			AlsoKnownAs:             "", | 			AlsoKnownAs:             "", | ||||||
| 			PrivateKey:              &rsa.PrivateKey{}, | 			PrivateKey:              &rsa.PrivateKey{}, | ||||||
| 			PublicKey:               &rsa.PublicKey{}, | 			PublicKey:               &rsa.PublicKey{}, | ||||||
| 			PublicKeyURI:            "http://localhost:8080/users/the_mighty_zork/publickey", | 			PublicKeyURI:            "http://localhost:8080/users/the_mighty_zork#main-key", | ||||||
| 			SensitizedAt:            time.Time{}, | 			SensitizedAt:            time.Time{}, | ||||||
| 			SilencedAt:              time.Time{}, | 			SilencedAt:              time.Time{}, | ||||||
| 			SuspendedAt:             time.Time{}, | 			SuspendedAt:             time.Time{}, | ||||||
|  | @ -398,16 +398,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { | ||||||
| 			URI:                     "http://localhost:8080/users/1happyturtle", | 			URI:                     "http://localhost:8080/users/1happyturtle", | ||||||
| 			URL:                     "http://localhost:8080/@1happyturtle", | 			URL:                     "http://localhost:8080/@1happyturtle", | ||||||
| 			LastWebfingeredAt:       time.Time{}, | 			LastWebfingeredAt:       time.Time{}, | ||||||
| 			InboxURL:                "http://localhost:8080/users/1happyturtle/inbox", | 			InboxURI:                "http://localhost:8080/users/1happyturtle/inbox", | ||||||
| 			OutboxURL:               "http://localhost:8080/users/1happyturtle/outbox", | 			OutboxURI:               "http://localhost:8080/users/1happyturtle/outbox", | ||||||
| 			SharedInboxURL:          "", | 			FollowersURI:            "http://localhost:8080/users/1happyturtle/followers", | ||||||
| 			FollowersURL:            "http://localhost:8080/users/1happyturtle/followers", | 			FollowingURI:            "http://localhost:8080/users/1happyturtle/following", | ||||||
| 			FeaturedCollectionURL:   "http://localhost:8080/users/1happyturtle/collections/featured", | 			FeaturedCollectionURI:   "http://localhost:8080/users/1happyturtle/collections/featured", | ||||||
| 			ActorType:               gtsmodel.ActivityStreamsPerson, | 			ActorType:               gtsmodel.ActivityStreamsPerson, | ||||||
| 			AlsoKnownAs:             "", | 			AlsoKnownAs:             "", | ||||||
| 			PrivateKey:              &rsa.PrivateKey{}, | 			PrivateKey:              &rsa.PrivateKey{}, | ||||||
| 			PublicKey:               &rsa.PublicKey{}, | 			PublicKey:               &rsa.PublicKey{}, | ||||||
| 			PublicKeyURI:            "http://localhost:8080/users/1happyturtle/publickey", | 			PublicKeyURI:            "http://localhost:8080/users/1happyturtle#main-key", | ||||||
| 			SensitizedAt:            time.Time{}, | 			SensitizedAt:            time.Time{}, | ||||||
| 			SilencedAt:              time.Time{}, | 			SilencedAt:              time.Time{}, | ||||||
| 			SuspendedAt:             time.Time{}, | 			SuspendedAt:             time.Time{}, | ||||||
|  | @ -443,16 +443,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { | ||||||
| 			URI:                   "https://fossbros-anonymous.io/users/foss_satan", | 			URI:                   "https://fossbros-anonymous.io/users/foss_satan", | ||||||
| 			URL:                   "https://fossbros-anonymous.io/@foss_satan", | 			URL:                   "https://fossbros-anonymous.io/@foss_satan", | ||||||
| 			LastWebfingeredAt:     time.Time{}, | 			LastWebfingeredAt:     time.Time{}, | ||||||
| 			InboxURL:              "https://fossbros-anonymous.io/users/foss_satan/inbox", | 			InboxURI:              "https://fossbros-anonymous.io/users/foss_satan/inbox", | ||||||
| 			OutboxURL:             "https://fossbros-anonymous.io/users/foss_satan/outbox", | 			OutboxURI:             "https://fossbros-anonymous.io/users/foss_satan/outbox", | ||||||
| 			SharedInboxURL:        "", | 			FollowersURI:          "https://fossbros-anonymous.io/users/foss_satan/followers", | ||||||
| 			FollowersURL:          "https://fossbros-anonymous.io/users/foss_satan/followers", | 			FollowingURI:          "https://fossbros-anonymous.io/users/foss_satan/following", | ||||||
| 			FeaturedCollectionURL: "https://fossbros-anonymous.io/users/foss_satan/collections/featured", | 			FeaturedCollectionURI: "https://fossbros-anonymous.io/users/foss_satan/collections/featured", | ||||||
| 			ActorType:             gtsmodel.ActivityStreamsPerson, | 			ActorType:             gtsmodel.ActivityStreamsPerson, | ||||||
| 			AlsoKnownAs:           "", | 			AlsoKnownAs:           "", | ||||||
| 			PrivateKey:            nil, | 			PrivateKey:            nil, | ||||||
| 			PublicKey:             &rsa.PublicKey{}, | 			PublicKey:             &rsa.PublicKey{}, | ||||||
| 			PublicKeyURI:          "http://fossbros-anonymous.io/users/foss_satan#publickey", | 			PublicKeyURI:          "http://fossbros-anonymous.io/users/foss_satan#main-key", | ||||||
| 			SensitizedAt:          time.Time{}, | 			SensitizedAt:          time.Time{}, | ||||||
| 			SilencedAt:            time.Time{}, | 			SilencedAt:            time.Time{}, | ||||||
| 			SuspendedAt:           time.Time{}, | 			SuspendedAt:           time.Time{}, | ||||||
|  | @ -1034,7 +1034,7 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit | ||||||
| 		URLMustParse("https://fossbros-anonymous.io/users/foss_satan"), | 		URLMustParse("https://fossbros-anonymous.io/users/foss_satan"), | ||||||
| 		time.Now(), | 		time.Now(), | ||||||
| 		dmForZork) | 		dmForZork) | ||||||
| 	sig, digest, date := getSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURL)) | 	sig, digest, date := getSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI)) | ||||||
| 
 | 
 | ||||||
| 	return map[string]ActivityWithSignature{ | 	return map[string]ActivityWithSignature{ | ||||||
| 		"dm_for_zork": { | 		"dm_for_zork": { | ||||||
|  | @ -1050,6 +1050,8 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit | ||||||
| // the HTTP Signature for the given activity, public key ID, private key, and destination. | // the HTTP Signature for the given activity, public key ID, private key, and destination. | ||||||
| func getSignatureForActivity(activity pub.Activity, pubKeyID string, privkey crypto.PrivateKey, destination *url.URL) (signatureHeader string, digestHeader string, dateHeader string) { | func getSignatureForActivity(activity pub.Activity, pubKeyID string, privkey crypto.PrivateKey, destination *url.URL) (signatureHeader string, digestHeader string, dateHeader string) { | ||||||
| 
 | 
 | ||||||
|  | 	streams.NewActivityStreamsPerson() | ||||||
|  | 
 | ||||||
| 	// create a client that basically just pulls the signature out of the request and sets it | 	// create a client that basically just pulls the signature out of the request and sets it | ||||||
| 	client := &mockHTTPClient{ | 	client := &mockHTTPClient{ | ||||||
| 		do: func(req *http.Request) (*http.Response, error) { | 		do: func(req *http.Request) (*http.Response, error) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue